为什么更喜欢组合而不是继承?每种方法都有哪些权衡?什么时候应该选择继承而不是组合?
当前回答
继承带来了所有不可否认的好处,下面是它的一些缺点。
继承的缺点:
您不能在运行时更改从超级类继承的实现(显然是因为继承是在编译时定义的)。继承将子类暴露给其父类实现的细节,这就是为什么人们常说继承破坏了封装(从某种意义上讲,您真正需要关注的是接口而不是实现,所以通过子类重用并不总是首选的)。继承提供的紧密耦合使得子类的实现与超级类的实现紧密结合,父类实现中的任何更改都将迫使子类进行更改。子类的过度重用会使继承堆栈变得很深,也很混乱。
另一方面,通过对象获取对其他对象的引用,在运行时定义对象组合。在这种情况下,这些对象将永远无法访问彼此的受保护数据(没有封装中断),并将被迫尊重彼此的接口。在这种情况下,实现依赖性也会比继承的情况下少得多。
其他回答
我的经验法则是:在使用继承之前,考虑组合是否更有意义。
原因:子类化通常意味着更复杂和更紧密,即更难在不出错的情况下进行更改、维护和扩展。
Sun的Tim Boudreau给出了一个更完整、更具体的答案:
在我看来,使用继承的常见问题是:无辜的行为可能会产生意想不到的结果——这方面的经典例子是调用超类中的可重写方法构造函数,在子类实例字段初始化。在一个完美的世界里,没有人会这样做。这是不是一个完美的世界。它给子类提供了不正当的诱惑,让它们对方法调用的顺序等做出假设——这种假设往往不会如果超类可以随着时间的推移而发展,那么它是稳定的。另请参见我的烤面包机和咖啡壶类比。类变得更重了——你不一定知道你的超类在其构造函数中做了什么工作,或者它需要多少内存使用。因此,构建一些无辜的轻量级对象可以比你想象的要贵得多,如果超类进化它鼓励子类的爆炸。类加载需要时间,更多的类需要内存。在你与NetBeans规模的应用程序打交道,但在那里,我们有了真正的例如,由于第一次显示菜单触发了大量的类加载。我们通过移动到更多的声明性语法和其他技术,但这需要花费时间修复。这使得以后改变事情变得更加困难——如果你公开了一个类,那么交换超类会破坏子类-这是一个选择,一旦你公开了代码,你就结婚了所以如果你不改变你的真实功能超类,如果你使用,而不是扩展你需要的东西。例如,子类化JPanel-这通常是错误的;如果子类是在公共场所,你永远不会有机会重新审视这个决定。如果它作为JComponent getThePanel()访问,您仍然可以这样做(提示:将组件的模型作为API公开)。对象层次结构不可缩放(或使其稍后缩放比提前规划要困难得多)-这是经典的“太多层”问题我将在下面讨论AskTheOracle模式如何解决它(尽管它可能冒犯OOP纯粹主义者)。...如果你允许继承遗产,我会怎么做带着一粒盐是:永远不显示字段,常量除外方法应为抽象方法或最终方法不从超类构造函数调用任何方法...所有这些都不适用于小型项目,而适用于大型项目私人课程比公共课程
我同意@帕维尔的说法,他说,有地方可以组成,也有地方可以继承。
我认为如果你的答案是肯定的,那么应该使用继承。
您的类是从多态性中受益的结构的一部分吗?例如,如果您有一个Shape类,它声明了一个名为draw()的方法,那么我们显然需要Circle和Square类作为Shape的子类,这样它们的客户端类将依赖于Shape而不是特定的子类。您的类是否需要重用其他类中定义的任何高级交互?如果没有继承,模板方法设计模式将不可能实现。我相信所有可扩展框架都使用这种模式。
然而,如果您的意图纯粹是代码重用,那么组合很可能是更好的设计选择。
继承在子类和超级类之间建立了牢固的关系;子类必须了解超级类的实现细节。创建超级类要困难得多,因为你必须考虑如何扩展它。您必须仔细记录类不变量,并说明其他可重写方法在内部使用的方法。
如果层次结构真的代表了一种is-a-关系,那么继承有时是有用的。它与开放-封闭原则有关,该原则规定类应该封闭以进行修改,但开放以进行扩展。这样你就可以拥有多态性;需要一个处理超类型及其方法的泛型方法,但通过动态调度调用子类方法。这是灵活的,有助于创建间接,这在软件中是必不可少的(对实现细节了解较少)。
然而,继承很容易被过度使用,并且会产生额外的复杂性,导致类之间存在硬依赖关系。此外,由于层和方法调用的动态选择,很难理解程序执行过程中发生的事情。
我建议使用作曲作为默认设置。它更加模块化,并提供了后期绑定的好处(您可以动态更改组件)。另外,单独测试这些东西更容易。如果您需要使用类中的方法,则不必强制使用某种形式(Liskov替换原则)。
我听过的一条经验法则是,当它是一种“is-A”关系时,应该使用继承;当它是“has-A”关系的时候,应该使用组合。尽管如此,我觉得你应该始终倾向于构图,因为它消除了很多复杂性。
另一个非常实用的原因是,倾向于组合而不是继承,这与域模型以及将其映射到关系数据库有关。将继承映射到SQL模型真的很难(你最终会遇到各种棘手的变通方法,比如创建不经常使用的列、使用视图等)。一些ORML试图处理这个问题,但它总是很快变得复杂。通过两个表之间的外键关系可以很容易地对组合进行建模,但继承要困难得多。