为什么更喜欢组合而不是继承?每种方法都有哪些权衡?什么时候应该选择继承而不是组合?


当前回答

我的经验法则是:在使用继承之前,考虑组合是否更有意义。

原因:子类化通常意味着更复杂和更紧密,即更难在不出错的情况下进行更改、维护和扩展。

Sun的Tim Boudreau给出了一个更完整、更具体的答案:

在我看来,使用继承的常见问题是:无辜的行为可能会产生意想不到的结果——这方面的经典例子是调用超类中的可重写方法构造函数,在子类实例字段初始化。在一个完美的世界里,没有人会这样做。这是不是一个完美的世界。它给子类提供了不正当的诱惑,让它们对方法调用的顺序等做出假设——这种假设往往不会如果超类可以随着时间的推移而发展,那么它是稳定的。另请参见我的烤面包机和咖啡壶类比。类变得更重了——你不一定知道你的超类在其构造函数中做了什么工作,或者它需要多少内存使用。因此,构建一些无辜的轻量级对象可以比你想象的要贵得多,如果超类进化它鼓励子类的爆炸。类加载需要时间,更多的类需要内存。在你与NetBeans规模的应用程序打交道,但在那里,我们有了真正的例如,由于第一次显示菜单触发了大量的类加载。我们通过移动到更多的声明性语法和其他技术,但这需要花费时间修复。这使得以后改变事情变得更加困难——如果你公开了一个类,那么交换超类会破坏子类-这是一个选择,一旦你公开了代码,你就结婚了所以如果你不改变你的真实功能超类,如果你使用,而不是扩展你需要的东西。例如,子类化JPanel-这通常是错误的;如果子类是在公共场所,你永远不会有机会重新审视这个决定。如果它作为JComponent getThePanel()访问,您仍然可以这样做(提示:将组件的模型作为API公开)。对象层次结构不可缩放(或使其稍后缩放比提前规划要困难得多)-这是经典的“太多层”问题我将在下面讨论AskTheOracle模式如何解决它(尽管它可能冒犯OOP纯粹主义者)。...如果你允许继承遗产,我会怎么做带着一粒盐是:永远不显示字段,常量除外方法应为抽象方法或最终方法不从超类构造函数调用任何方法...所有这些都不适用于小型项目,而适用于大型项目私人课程比公共课程

其他回答

更喜欢组合而不是继承,因为它更具延展性/更易于稍后修改,但不要使用组合始终方法。通过组合,使用依赖注入/设置器可以很容易地动态改变行为。继承更为严格,因为大多数语言不允许从多个类型派生。因此,一旦你从A型衍生出,鹅或多或少都是熟的。

我的上述酸性测试是:

TypeB是否希望公开TypeA的完整接口(所有公共方法都不少于),以便在需要TypeA的地方使用TypeB?表示继承。例如,赛斯纳双翼飞机将暴露出飞机的完整界面,如果不是更多的话。因此,它适合于从飞机衍生。TypeB是否只想要TypeA暴露的部分行为?表示需要合成。例如,鸟可能只需要飞机的飞行行为。在这种情况下,将其作为接口/类/两者提取出来并使其成为两个类的成员是有意义的。

更新:刚刚回到我的答案,现在看来,如果没有具体提到芭芭拉·里斯科夫的里斯科夫替代原则作为“我应该继承这种类型吗?”的测试,它似乎是不完整的

除了是/有一个考虑因素之外,还必须考虑对象必须经历的继承的“深度”。任何超过五或六级继承深度的内容都可能会导致意外的强制转换和装箱/拆箱问题,在这种情况下,编写对象可能是明智的。

简单地说,实现就像你(类)应该遵循的规则

但是扩展就像对很多类使用公共代码,也许只是其中一个需要重写。

另一个非常实用的原因是,倾向于组合而不是继承,这与域模型以及将其映射到关系数据库有关。将继承映射到SQL模型真的很难(你最终会遇到各种棘手的变通方法,比如创建不经常使用的列、使用视图等)。一些ORML试图处理这个问题,但它总是很快变得复杂。通过两个表之间的外键关系可以很容易地对组合进行建模,但继承要困难得多。

合成与继承是一个广泛的主题。对于什么更好,没有真正的答案,因为我认为这一切都取决于系统的设计。

通常,对象之间的关系类型为选择其中一个对象提供了更好的信息。

如果关系类型是“is-A”关系,则继承是更好的方法。否则关系类型为“HAS-A”关系,则组合将更接近。

这完全取决于实体关系。