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


当前回答

这条规则完全是无稽之谈。为什么?

原因是,在任何情况下,都可以区分是使用组合还是继承。这取决于对一个问题的回答:“是什么?是什么?”或“有什么?”其他的东西“。

你不能“宁愿”把某样东西变成另一样东西或拥有另一样。严格的逻辑规则适用。

也没有“人为的例子”,因为在任何情况下都可以给出这个问题的答案。

如果你不能回答这个问题,还有其他问题。这包括类的责任重叠,这通常是错误使用接口的结果,较少是通过在不同类中重写相同的代码。

为了避免这种情况,我还建议为类使用与它们的职责完全相似的好名称。

其他回答

当您想要“复制”/公开基类的API时,可以使用继承。当您只想“复制”功能时,请使用委派。

其中的一个例子是:您想要从列表中创建堆栈。堆栈只有pop、push和peek。考虑到堆栈中不需要push_back、push_front、removeAt等功能,您不应该使用继承。

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

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

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

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

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

Why?

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

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

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

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

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

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

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

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

继承是非常诱人的,尤其是来自程序领域的继承,它通常看起来很优雅。我的意思是,我需要做的就是将这一点功能添加到其他类中,对吗?嗯,问题之一是继承可能是最糟糕的耦合形式

基类通过以受保护成员的形式向子类公开实现细节来打破封装。这会使您的系统变得僵化和脆弱。然而,更悲惨的缺陷是新的子类带来了继承链的所有包袱和观点。

《继承是邪恶的:DataAnnotationsModelBinder的史诗般的失败》一文在C#中讲述了一个这样的例子。它显示了继承在何时应该使用组合以及如何重构组合。

将遏制视为一种关系。汽车“有”发动机,人“有”名字等等。

把继承看作是一种关系。汽车“是”车辆,人“是”哺乳动物等。

我不认为这种做法值得赞扬。我直接从史蒂夫·麦康奈尔(Steve McConnell)的《代码大全》第二版第6.3节中得到了答案。