面向对象的缺点
通常我不想知道面向对象缺点的具体情况,但是当我最近参加的一次采访中有一个争论的时候,我感到有些奇怪。 发布给我的问题是告诉我面向对象编程 (OOP)的一个缺点。 当时,我认为OOP是程序和function模型之后最成熟的编程水平。 所以我回答说,我根本没有看到任何消极的东西。
但是面试官说很less,如果他不介意的话,我让他列出一个。 他举了一个例子,我不好好消化。 他表示,OOP模式并不严格执行遗产规则,并引用卫星/火箭的例子,在火箭发射期间身体部位将定期瓦解以消除重量,并表示inheritance不支持这一点。
他的例子对我来说感觉很奇怪,原因就在于这个例子中inheritance的应用。
我明白,他给出的例子几乎没有任何意义,但我有这个疑问 –
我们可以在一个理想的面向对象的devise中dynamic地拔出类的层次结构(我对Java有信心,这是不可能的)吗?
我不完全了解他的例子。
然而,重要的是要明白,OOP对于可以自然模拟的事物是非常有效的,而对于其他事物(例如横切关注点或方面)是非常无效的。 这是面向对象的一个缺点。 另一个原因是由于dynamic调度,它经常招致一些运行成本。
另外,滥用面向对象进行不可感知的抽象是非常容易的。 有一个火箭inheritance身体就是一个例子。 我的经验是,当其他行为(如聚合)更合适时,新手开发人员不会信任也不会使用inheritance,或者他们过于渴望并且使用不当。 随着时间的推移,对机制的经验和理解得到改善。
我不确定他的意思是“OOP模式不严格执行inheritance规则”,因为OOP不是一个模式。 一个潜在的问题是可以写一个可能违反Liskov替代原则的子types,这样覆盖的方法就不会像重写的方法那样“至less”。 没有办法自动检查这个,所以有可能编写违反OOP原则的代码。
至于你的最后一个问题:“我们能否在一个理想的面向对象的devise中拔掉类的层次结构?”,我不确定你的意思。 如果您在运行时询问如何更改层次结构,并且使得在执行过程中某个子types关系不再存在,那么是的。 使用某些语言如Smalltalk是可能的。 有人会认为这是“更多的OOP”。 Smalltalk中,types支持的“方法”是根据当前层次结构和每个类的当前内容在方法调用点确定的。 虽然我喜欢smalltalk,但这是我没有疯狂的一个特性,因为我更喜欢编译时检查,而且运行时间更less。
只因为你有一把锤子,并不代表一切都是钉子。
我发现很多人在使用OOP作为编程风格时经常混淆何时应用合成与inheritance 。 你从面试问题中引用的例子似乎是这种混乱的例子。
我从经验中学到的一件事是,尽pipeinheritance是devise中实体之间expression“IS-A”关系的一个很好的范例, 但通常情况下,偏好合成而不是inheritance 。
但是让我们来看一下面试官问题的症结所在: OOP是什么时候成为范式的select? 。
OOP在大型多开发人员多模块项目中效果最佳。 对于“ 小型开发 ” – 比如脚本或变形处理,它可能需要很多开销而没有增加价值。 但是即使在这种情况下,除非你写弃权代码,否则我认为大的解决scheme通常是从小的解决scheme演变而来的,所以尽早build立结构和关注的分离可以让你后悔。
最终,面向对象编程还需要一定的devise严谨性和规划水平,以及对面向对象的核心原则的理解。 如果你不愿意花时间学习和理解这些原则…那么也许OOP编程不适合你。
除此之外,某些types的问题也适用于其他编程风格。 例如,变形处理对编程的function风格是相当可观的 – 在这种风格中,结果被计算并传递到连续的变换步骤以产生最终结果。 您必须search或查询某个域的某些匹配信息时遇到的问题很容易查询语言 (如SQL),而您在其中声明性地描述您的意图,而不是强制性地描述您的意图。
能够识别你正在解决哪种问题在select使用哪种语言或工具方面有很长的路要走。 正确的工具可以使工作变得更容易,错误的工具可以使工作变得更加困难或不可能。
虽然我同意面试官的结论(OOP是有缺陷的),但他的逻辑似乎是无稽之谈。 这听起来好像是在批评他不明白的东西,因为没有一个有能力的OO程序员会让火箭从推进器中inheritance下来。
不过,人们对OOP做出了很好的批评。 例如,史蒂夫·叶格的“名词王国的执行” 。
他的榜样毫无意义。 火箭不会从身体inheritance。 它“有”一个身体。 这是遏制。 所以有一点你可以“删除”放在火箭上的那部分。
虽然我不完全理解所给出的例子,但是听起来像是对我的组合,我会给OOP一个缺点
OO很难testing
-
没有直接访问读取/写入类variables/属性 – 可能需要引入getter和seeters破坏封装。
-
inheritance可以改变子类中方法的行为
-
对象有状态。 由于我们依赖于公共接口,所以很难生成这些状态。
-
具有凝聚力的class级可能难以testing,因为这可能意味着class级做得比指定的要多。
我可以看到他驾驶的点。 我认为这个论点基本上是以inheritance的缺点为中心的 – 如果你不小心,或者为了继续扩展function而inheritance了太多时间,那么最终可能会导致一大堆的冗余特性,的凝聚力。
他的类比是有效的,如果你考虑到,一旦火箭已经燃烧了一段燃料,那么这段燃料就会变得多余,因此也就是自重。 火箭可以抛弃这个部分,但是我不认为有可能排除一个你不想inheritance的部分(尽pipe纠正了我,如果我错了,因为这听起来很有用)。
火箭的例子可能暗示了在许多面向对象的语言中,一个对象的types是不可变的? 也就是说,如果我有一个Student
对象,并且它所代表的人完成学习并成为一名员工,我不能将Student
对象转换为一个Employee
对象。 我必须创build一个新的,在这个过程中失去对象的身份,因此不得不更新所有的引用指向前一名学生。
当然,这样的function会干扰静态types(types不变,对象的types是指向它的任何引用的静态types的子types)。 但在某些情况下,灵活性可能是值得的。
经典的是喜欢构图而不是inheritance。 inheritance是为了捕获你的系统沿着一个缝隙的抽象。
如果你不太清楚这个抽象,那么是的,你会在你的devise中得到奇怪的力量。 但是这是一个世界上的编程问题,抽象对于你所要达到的目标来说是不太正确的。 不是他们会永远是完美的。
阅读这篇关于为什么Extends是邪恶的优秀文章。 这就是说我不能说这是一个面向对象的编程。
我想你的面试官的太空船的理想是从2001年的黑色方尖碑:太空奥德赛 。
我认为OOP最大的缺点是缺乏对高阶函数的支持。 尽pipe您可以传入包含方法的对象,但这是冗长而低效的。 能够直接将函数传递给另一个函数会好得多。
关于父类中不需要的方法:它们不是坏build筑的标志吗? 我的意思是,你不应该在计划阶段中分离出每个类所需要的function,然后有一个从另一个类派生出来的类链。
例如,我们有A,B,C和D类.A类有B,C和D类的方法需要。 B类具有D类方法所需的方法。 所以我们可以从B中从B和B派生出D类。另一方面,我们将从A派生C,因为C不需要B具有。 所以我们基本上通过在层次结构中增加一个额外的类来解决不需要的东西的问题。 当然,如果这样的事情在计划阶段没有被抓到,而B级被用作基础class级,将Bclass级拆分成两个class级将会更加困难,但是通过一些额外的努力,解决了不必要的行李问题。
我敢肯定,我从来没有听说过Java这样的静态types语言,它能够将对象转换为另一个对象,同时保持对象引用的完整,但是我相信你可以用代表来伪造它。也就是说,没有一些从根本上来说好的想法,可能会是一团糟。
我不知道面试官是如何提出这个问题的,或者你是如何回应的,但是OOP最大的缺点之一并不是OOP本身。 OOP的糟糕用法以及人们尝试创build的糟糕的“IS-A”关系。 如果你能想出创build一个inheritance层次的唯一原因就是重用代码,那么这是一个糟糕的理由。 AOP可以说是一个更好的方法。 (在你火焰还是投票之前,请注意我说“可以”)
OOP是关于接口和types,而不是关于实现。 知道一个对象可以返回一份候选人名单是我所需要知道的。 无论是来自RDBMS,面向列的数据库,Web服务甚至电子表格都不重要。 我知道我可以将该对象作为parameter passing给其他方法,他们可以调用“FetchJobCandidates”并知道它将获得应聘者。 一路上有陷阱,但主要是如果你想到的是基于他们暴露给世界其他地方的课程,而不是他们在内部做什么,你是一个更好的基础海事组织。
我们可以在OOP中使用这个吗?
RocketShip.BodyPart1 =什么也没有
RocketShip.BodyPart1.isDetached = false?
我相信这不是面向对象本身是错误的,但它是如何使用的编程语言的限制。
看看这些文章 。 对面向对象的一些缺点有各种各样的解释,特别是把事物强行分层的风险,就像面试者的例子。
build模一个改变其子类的对象的一种方法是使用状态模式 。 使用您的面试问题和维基百科的一些帮助,下面是一些Java代码,展示了如何在火箭经历各个阶段时模拟状态转换。 我没有显示出司令部的阶段,但是他们通过月球下降,登月,月球交会和返回地球的方式遵循相似的模式。 你可能会发现更多有趣的行为添加到ISpaceShipState接口。
public class SpaceShip { private ISpaceShipState m_state = new SaturnFiveState(); public void setState(ISpaceShipState state) { m_state = state; } public void jettison() { m_state = m_state.transition(); } public int getStageNumber() { return m_stage.getStageNumber(); } public int getNumberOfRocketMotors() { return m_stage.getNumberOfRocketMotors(); } public String getRocketMotorTypeName() { return m_stage.getRocketMotorTypeName(); } } interface ISpaceShipState { public ISpaceShipState transition(); public int getStageNumber(); public int getNumberOfRocketMotors(); public String getRocketMotorTypeName(); } public class SaturnFiveState implements ISpaceShipState { public ISpaceShipState transition() { return new SaturnFiveSecondStageState(); } public int getStageNumber() { return 1; } public int getNumberOfRocketMotors() { return 5; } public String getRocketMotorTypeName() { return "F-1"; } } public class SaturnFiveSecondStageState implements ISpaceShipState { public ISpaceShipState transition() { return new SaturnFiveThirdStageState(); } public int getStageNumber() { return 2; } public int getNumberOfRocketMotors() { return 5; } public String getRocketMotorTypeName() { return "J-2"; } } public class SaturnFiveThirdStageState implements ISpaceShipState { public ISpaceShipState transition() { return new SaturnFiveCommandModuleState(); } public int getStageNumber() { return 3; } public int getNumberOfRocketMotors() { return 1; } public String getRocketMotorTypeName() { return "J-2"; } }
我们可以在一个理想的面向对象的devise中dynamic地拔出类的层次结构吗(我对Java是不可能的)
使用reflection模拟Java中的dynamictypes是可能的,尽pipe与dynamictypes语言中的相同的代码相比,代码非常笨拙。
我认为它的主要缺点是直接来自它的力量。 在OOP中最常见的事情之一是在某些地方用派生类replace父类,以专门化代码的行为。 那么,如果不这样做,就很容易使得在调用者代码中做出的一些假设无效; 在审查代码时可能很难find,因为你所看到的types并不是实际使用的types。
简而言之:可以使多态性不明确,从而容易忽略派生类中的奇怪行为。
其他人已经覆盖了它,但是如果你仔细想想,并不是每件事物都是对象。 在计算中,没有什么是真正的对象。 我们只是抽象出一个对象的概念。
例如,岩石是由物质构成的。 我们可以将岩石视为计算术语(OOP术语)中的对象,但是这并不能帮助围绕岩石的概念进行编程。
如果您考虑面向对象编程的主要概念, 封装,多态性,类,inheritance……所有这些核心OOP概念都存在问题。
也许像圆形椭圆问题