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


当前回答

简而言之,我同意“比起继承,我更喜欢成分”,但对我来说,这听起来常常像“比起可口可乐,我更爱土豆”。有继承的地方,也有合成的地方。你需要了解差异,然后这个问题就会消失。这对我来说真正的意义是“如果你要使用继承——再想想,你很可能需要复合”。

当你想吃的时候,你应该更喜欢土豆而不是可口可乐,当你想喝的时候,应该更喜欢可口可乐而不是土豆。

创建子类不仅仅意味着调用超类方法的便捷方式。当子类在结构上和功能上都是一个超级类时,您应该使用继承,因为它可以用作超级类,并且您将使用它。如果不是这样的话,那不是继承,而是其他东西。合成是指你的对象由另一个组成,或者与它们有某种关系。

所以对我来说,如果有人不知道自己是否需要继承或组成,真正的问题是他不知道自己是想喝酒还是想吃饭。多想想你的问题领域,更好地理解它。

其他回答

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

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

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

这完全取决于实体关系。

理解这一点的一个简单方法是,当您需要类的对象与其父类具有相同的接口时,应该使用继承,这样它就可以被视为父类的对象(上广播)。此外,派生类对象上的函数调用在代码中的任何地方都保持不变,但要调用的特定方法将在运行时确定(即,低级实现不同,高级接口保持不变)。

当您不需要新类具有相同的接口时,即您希望隐藏该类的用户不需要知道的类实现的某些方面时,应使用组合。因此,组合更多的是支持封装(即隐藏实现),而继承意味着支持抽象(即提供某种东西的简化表示,在这种情况下,相同的接口用于具有不同内部结构的一系列类型)。

这两种方式可以很好地生活在一起,并相互支持。

组合只是将其模块化:您创建类似于父类的接口,创建新对象并将调用委托给它。如果这些对象不需要彼此了解,那么组合是非常安全且易于使用的。这里有很多可能性。

然而,如果父类出于某种原因需要访问“子类”为没有经验的程序员提供的函数,那么它可能看起来是一个使用继承的好地方。父类可以只调用它自己的抽象“foo()”,该抽象被子类覆盖,然后它可以将值赋给抽象基。

这看起来是一个不错的想法,但在许多情况下,只给类一个实现foo()的对象(或者甚至手动设置foo)提供的值)要好于从需要指定foo函数的基类继承新类。

Why?

因为继承是传递信息的一种糟糕方式。

组合在这里有一个真正的优势:关系可以颠倒:“父类”或“抽象工作者”可以聚合实现特定接口的任何特定“子”对象+任何子对象都可以设置在接受其类型的任何其他类型的父类中。而且可以有任意数量的对象,例如MergeSort或QuickSort可以对实现抽象比较接口的任何对象列表进行排序。或者换一种说法:实现“foo()”的任何一组对象和可以使用具有“foo)”的对象的其他一组对象都可以一起玩。

我可以想到使用继承的三个真正原因:

您有许多具有相同接口的类,您希望节省编写它们的时间必须为每个对象使用相同的基类您需要修改私有变量,在任何情况下都不能是公共的

如果这些都是真的,那么可能需要使用继承。

使用原因1没有什么不好的,在对象上有一个坚实的界面是非常好的。这可以使用组合或继承来完成,没有问题——如果这个接口简单且不改变。通常,继承在这里非常有效。

如果原因是第二,那就有点棘手了。你真的只需要使用相同的基类吗?一般来说,仅仅使用相同的基类是不够的,但这可能是框架的一个要求,这是一个无法避免的设计考虑。

然而,如果您想使用私有变量,即案例3,那么您可能会遇到麻烦。如果您认为全局变量不安全,那么应该考虑使用继承来访问私有变量也不安全。请注意,全局变量并不是那么糟糕——数据库本质上是一组全局变量。但如果你能处理,那就很好了。

我听过的一条经验法则是,当它是一种“is-A”关系时,应该使用继承;当它是“has-A”关系的时候,应该使用组合。尽管如此,我觉得你应该始终倾向于构图,因为它消除了很多复杂性。

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

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