OOP的缺点?

时间:2010-05-17 22:21:21

标签: java c++ oop inheritance

通常我不想知道OOP缺点的具体细节,但是当我最近参加的一次面试时,我觉得有点奇怪。发给我的问题是告诉我object-oriented programming(OOP)的一个缺点。那时,我认为OOP是程序和功能模型之后最成熟的编程水平。所以我回答他说我根本没有看到任何负片。

但是面试官说的很少,如果他不介意的话我就让他列出一个。他举了一个例子,我不能很好地消化。他说,OOP模式并没有严格执行继承规则,并引用了卫星/火箭示例,其中身体部位会在火箭发射期间定期瓦解以消除重量,并表示继承并不支持这一点。

他的例子对我来说非常奇怪,原因是继承应用于这个例子。

我明白他给出的例子几乎没有任何意义,但我怀疑 -

  

我们可以拔掉类层次结构   动态(我有点自信   在Java中它是不可能的)在理想中   面向对象的设计?

18 个答案:

答案 0 :(得分:34)

  

只因为你有一把锤子,   并不意味着一切都是钉子。

我发现在使用OOP作为编程风格时,很多人经常会混淆何时应用compositioninheritance您从面试问题中引用的示例似乎是这种混乱的一种情况。

我从经验中学到的一点是,虽然继承是设计中实体之间表达“IS-A”关系的一个很好的范例,但通常更好地支持组合而不是继承

但是让我们来看看面试官问题的症结所在: OOP什么时候可以选择范式?

OOP最适合大规模,多开发人员,多模块项目。对于“开发小型” - 例如脚本或变换处理,它可能需要大量的开销而不必增加价值。但即使在这些情况下,除非你正在编写丢失代码,否则我认为大型解决方案通常是从较小的解决方案发展而来的,所以尽早建立结构和关注点分离可以在以后挽救你的悲痛。

最终,OOP编程还需要一定程度的设计严谨性和规划,以及对core principles of object orientation的理解。 如果您不愿意花时间学习并理解这些原则......那么,或许OOP编程对您来说不是

除此之外,某些类型的问题也适用于其他编程风格。例如,变换处理对于functional style of programming是非常值得称赞的,其中计算结果并将其传递到连续的变换步骤以产生最终结果。您必须搜索或查询域以获取某些匹配信息的问题适用于query languages(如SQL),您可以在其中以声明方式而非强制性地描述您的意图。

能够识别您正在解决哪种问题对于选择使用哪种语言或工具大有帮助。正确的工具可以使工作变得更加轻松......错误一个人可以让它更难 - 或者不可能。

答案 1 :(得分:18)

我不完全理解他的榜样。

然而,重要的是要理解OOP对于可以用它自然建模的事物非常有效,并且对其他事物(例如,横切关注点或方面)非常无效。这是OOP的一个缺点。另一个原因是,由于动态调度,它经常会产生一些运行时成本。

此外,滥用OOP非常容易进行无法识别的抽象。火箭继承自身体就是一个例子。我的经验是,当其他行为(如聚合)更合适时,新手开发人员或者不信任并且不使用继承,或者他们过于渴望并且错误地使用它。随着时间的推移,对该机制的经验和理解会有所改善。

我不确定他的意思是“OOP模式不严格实现继承规则”,因为OOP不是模式。一个潜在的问题是,人们可以编写一个可以违反Liskov替换原则的子类型,因此覆盖方法不会像被覆盖的方法那样“至少”行为。无法自动检查这一点,因此可以编写违反OOP原则的代码。

至于你的最后一个问题“我们可以在理想的面向对象设计中拔掉类层次结构吗?”,我不确定你的意思。如果您要求在运行时更改层次结构,并使其从执行中的某个点不再存在子类型关系,那么是。对于某些语言,例如Smalltalk,它是可能的。有人会认为这是“更多OOP”。在smalltalk中,类型支持的“方法”是在方法调用时根据当前层次结构和每个类的当前内容确定的。虽然我喜欢smalltalk,但这是一个我并不疯狂的功能,因为我更喜欢编译时检查,而不是运行时的惊喜。

答案 2 :(得分:15)

虽然我同意采访者的结论(OOP有缺陷),但他的逻辑似乎是无稽之谈。听起来他批评的是他不理解的东西,因为没有一个称职的OO程序员会让火箭继承其推进器。

然而,人们对OOP提出了很好的批评。例如,Steve Yegge的Execution in the Kingdom of Nouns

答案 3 :(得分:11)

他的榜样毫无意义。火箭不会从身体继承。它“拥有”一个身体。这是遏制。所以有一点你会“删除”附在火箭上的部分,因为它被抛弃了。

答案 4 :(得分:9)

虽然我不完全理解给出的例子,因为它听起来像是对我的构图我会给OOP带来一个缺点

OO难以测试

  • 无法直接访问读/写类变量/属性 - 可能需要引入打破封装的getter和seeter。

  • 继承可以更改子类中方法的行为

  • 对象有状态。由于我们依赖于公共接口,因此很难生成这些状态。

  • 具有loh内聚力的类可能很难测试,因为这可能意味着该类的操作超出了指定范围。

答案 5 :(得分:3)

我可以看出他驾驶的重点。我认为这个论点基本上围绕着继承的劣势 - 如果你不小心,或者如果你继承了太多次来继续扩展功能,你最终可能会遇到一个带有冗余功能和缺乏的类的臃肿混乱凝聚力。

如果你考虑到这一点,他的类比就有效了,一旦火箭燃烧了一段中的燃料,这段就变得多余了,因此它的重量也很大。火箭可以抛弃该段,但我认为不可能排除你不想继承的类的部分(虽然如果我错了,请纠正我,因为它听起来很有用)。

答案 6 :(得分:2)

火箭的例子可能提到了这样一个事实:在许多面向对象的语言中,对象的类型是不可变的吗?也就是说,如果我有一个Student对象,并且它所代表的人完成学业并成为一名员工,我就无法将Student对象转换为Employee对象。我必须创建一个新的,在过程中丢失对象标识,因此必须更新指向前学生的所有引用。

当然,这样的功能会干扰静态类型(对象的类型是指向它的任何引用的静态类型的子类型的类型不变量)。但在某些情况下,灵活性可能是值得的。

答案 7 :(得分:1)

可能像圆椭圆问题

答案 8 :(得分:1)

其他人已经介绍过它,但如果你考虑一下,并不是一切都是对象。在计算中,没有什么是真正的对象。我们只是抽象出一个对象的概念。

例如,岩石由物质组成。我们可以将岩石视为计算术语中的对象(OOP术语),但这无助于围绕岩石概念进行编程。

如果你考虑面向对象编程的主要概念;封装,多态,类,继承......所有这些核心OOP概念都存在问题。

答案 9 :(得分:1)

对改变其子类的对象进行建模的一种方法是使用State pattern。使用你的面试问题和Wikipedia的一些帮助,这里有一些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";
    }
}
  

我们可以在理想的面向对象设计中动态地拔掉类层次结构(我对Java不可信)吗?

可以使用反射模拟Java中的动态类型,尽管与动态类型语言中的代码相比,代码非常笨拙。

答案 10 :(得分:1)

看看these articles。对OOP的一些缺点有各种解释,特别是强迫事物进入等级的危险,比如在面试官的例子中。

答案 11 :(得分:1)

我认为它的主要劣势来自它的力量。在OOP中最常见的事情之一是在某些地方用派生类替换父类,以专门化代码的行为。好吧,如果不仔细,很容易使调用者代码中的一些假设无效;在查看代码时可能很难发现,因为您看到的类型不是实际使用的类型。

简而言之:多态可以不明确,因此容易忽略派生类中的奇怪行为。

答案 12 :(得分:1)

我们可以说在OOP中使用它吗?

RocketShip.BodyPart1 =没有或

RocketShip.BodyPart1.isDetached = false?

我认为不是OOP本身就是错误,而是编程语言的限制是如何实现的。

答案 13 :(得分:1)

我确信我从来没有听说过类似Java的静态类型语言,它能够将对象变形为另一个,同时保持对象引用不变,但我相信你可以用代表等来伪造它。没有一些基本上好的想法,它可能会是一团糟。

我不知道面试官是如何提出这个问题或者你是如何回应的,但是OOP最大的缺点之一并不是OOP本身。这是OOP的糟糕使用和人们试图创建的糟糕的“IS-A”关系。如果您能够创建继承层次结构的唯一原因是重用代码,那么这是一个很糟糕的原因。 AOP可以被认为是一种更好的方法。 (在你投票或投票之前,请注意我说“可以”)

OOP是关于接口和类型,而不是关于实现。知道对象可以返回候选人列表是我需要知道的。无论是来自RDBMS,面向列的数据库,Web服务,还是电子表格都是无关紧要的。我知道我可以将该对象作为参数传递给其他方法,他们可以调用“FetchJobCandidates”并知道它将获得求职者。一路上有一些陷阱,但主要是如果你根据他们向世界其他地方展示的内容而不是内部的内容来考虑课程,那么你就是一个更好的立足点IMO。

答案 14 :(得分:1)

关于父类中不需要的方法: 它们不是糟糕建筑的标志吗?我的意思是你不应该在每个类所需的规划阶段中分离出一些类,然后从一个类派生出一个类吗?

例如,我们有A,B,C和D类.A类有方法类B,C和D需要。 B类有D类需要的方法。所以我们可以从A中得到B和B的D类。另一方面,我们将从A推导出C,因为C不需要B所具有的东西。所以我们基本上通过在层次结构中添加一个额外的类来解决不需要的东西的问题。当然,如果这样的事情没有在规划阶段被捕获并且B类被用作基类,那么将B类分成两个类将更加困难,但是通过一些额外的努力,解决了不必要的行李问题。

答案 15 :(得分:1)

我能用OOP想到的最大缺点是缺乏对高阶函数的支持。虽然您可以传入包含方法的对象,但这是冗长且低效的。能够将函数直接传递给另一个函数会好得多。

答案 16 :(得分:1)

阅读Why Extends are Evil上的这篇优秀文章。这就是说我不能说它是面向对象编程的con

我猜你的采访者对太空船的理想是Black Obelisk from 2001: Space Odyssey

答案 17 :(得分:1)

经典之作是更喜欢构图而不是继承。继承用于捕获系统随之变化的接缝的抽象。

如果你没有清楚地捕捉到这种抽象,那么是的,你会对你的设计产生奇怪的影响。但这是一个编程问题,在这个世界中,抽象并不适合你想要达到的目标。并不是说它们永远都是完美的。