为什么更喜欢组合而不是继承?每种方法都有哪些权衡?什么时候应该选择继承而不是组合?
当前回答
就我个人而言,我学会了总是喜欢组合而不是继承。没有可以用继承解决的编程问题,而不能用组合解决;尽管在某些情况下,您可能必须使用接口(Java)或协议(Obj-C)。因为C++不知道任何这类东西,所以您必须使用抽象基类,这意味着您无法完全摆脱C++中的继承。
组合通常更符合逻辑,它提供了更好的抽象、更好的封装、更好的代码重用(尤其是在非常大的项目中),并且不太可能因为您在代码中的任何地方进行了孤立的更改而在远处破坏任何东西。这也使得维护“单一责任原则”变得更容易,该原则通常被概括为“一个类的改变不应该有一个以上的原因”,这意味着每个类都是为了一个特定的目的而存在的,它只应该有与其目的直接相关的方法。此外,拥有一个非常浅的继承树,即使您的项目开始变得非常大,也可以更容易地保持概述。许多人认为遗产很好地代表了我们的现实世界,但事实并非如此。现实世界使用的合成比继承多得多。几乎每一个你能拿在手中的现实世界物体都是由其他更小的现实世界对象组成的。
不过,组合也有缺点。如果您完全跳过继承,只关注组合,您会注意到,如果您使用了继承,通常需要编写一些额外的代码行。你有时也会被迫重复自己,这违反了干原则(干=不要重复自己)。此外,组合通常需要委托,一个方法只是调用另一个对象的另一个方法,而没有其他代码围绕此调用。这种“双重方法调用”(可能很容易扩展到三重或四重方法调用,甚至更远)的性能要比继承差得多,因为继承只是继承父级的方法。调用继承的方法可能与调用非继承的方法一样快,也可能稍慢,但通常仍比两个连续的方法调用快。
您可能已经注意到,大多数OO语言不允许多重继承。虽然在一些情况下,多重继承确实可以为您带来一些东西,但这些都是例外,而不是规则。每当你遇到一个你认为“多重继承将是解决这个问题的一个非常酷的特性”的情况时,你通常都应该重新考虑继承,因为即使它可能需要一些额外的代码行,基于组合的解决方案通常会变得更加优雅、灵活和经得起未来考验。
继承确实是一个很酷的功能,但我担心它在过去几年中被过度使用了。人们将遗产视为一把能钉住一切的锤子,不管它实际上是一颗钉子、一颗螺丝钉,还是一个完全不同的东西。
其他回答
在Java或C#中,对象一旦实例化就不能更改其类型。
因此,如果您的对象需要显示为不同的对象或根据对象的状态或条件表现出不同的行为,那么请使用组合:参考状态和策略设计模式。
如果对象需要具有相同的类型,则使用继承或实现接口。
我同意@帕维尔的说法,他说,有地方可以组成,也有地方可以继承。
我认为如果你的答案是肯定的,那么应该使用继承。
您的类是从多态性中受益的结构的一部分吗?例如,如果您有一个Shape类,它声明了一个名为draw()的方法,那么我们显然需要Circle和Square类作为Shape的子类,这样它们的客户端类将依赖于Shape而不是特定的子类。您的类是否需要重用其他类中定义的任何高级交互?如果没有继承,模板方法设计模式将不可能实现。我相信所有可扩展框架都使用这种模式。
然而,如果您的意图纯粹是代码重用,那么组合很可能是更好的设计选择。
我听过的一条经验法则是,当它是一种“is-A”关系时,应该使用继承;当它是“has-A”关系的时候,应该使用组合。尽管如此,我觉得你应该始终倾向于构图,因为它消除了很多复杂性。
正如许多人所说的,我首先要检查是否存在“是”的关系。如果存在,我通常检查以下内容:
是否可以实例化基类。也就是说,基类是否可以是非抽象的。如果可以是非抽象的,我通常更喜欢写作
例如1。会计是员工。但我不会使用继承,因为Employee对象可以实例化。
例如2。书籍是SellingItem。SellingItem无法实例化-它是抽象概念。因此,我将使用遗传痤疮。SellingItem是一个抽象基类(或C#中的接口)
你觉得这种方法怎么样?
此外,我支持为什么要使用继承?
使用继承的主要原因不是作为组合的一种形式,而是为了让您可以获得多态行为。如果不需要多态性,那么可能不应该使用继承。
@马修。在https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448
继承的问题在于它可以用于两个正交的目的:接口(用于多态性)实现(用于代码重用)
参考
哪个班级设计更好?继承与聚合
理解这一点的一个简单方法是,当您需要类的对象与其父类具有相同的接口时,应该使用继承,这样它就可以被视为父类的对象(上广播)。此外,派生类对象上的函数调用在代码中的任何地方都保持不变,但要调用的特定方法将在运行时确定(即,低级实现不同,高级接口保持不变)。
当您不需要新类具有相同的接口时,即您希望隐藏该类的用户不需要知道的类实现的某些方面时,应使用组合。因此,组合更多的是支持封装(即隐藏实现),而继承意味着支持抽象(即提供某种东西的简化表示,在这种情况下,相同的接口用于具有不同内部结构的一系列类型)。