组成重于继承,但


13

我试图自学软件工程,遇到一些使我感到困惑的冲突信息。

我一直在学习OOP,以及什么是抽象类/接口以及如何使用它们,但是随后我读到,应该“偏重于继承而不是继承”。我知道组合是指一个类组成/创建另一个类的对象以利用/与该新对象的功能交互。

所以我的问题是...我应该不使用抽象类和接口吗?是否不创建抽象类并在具体类中扩展/继承该抽象类的功能,而是仅编写新对象以使用其他类的功能?

或者,我应该使用组合并从抽象类继承吗?两者结合使用?如果是这样,您能否提供一些示例,说明其工作方式及其好处?

因为我最熟悉PHP,所以在继续使用其他语言并转移我新获得的SE技能之前,我正在使用它来提高OOP / SE技能,因此,使用PHP的示例非常受赞赏。


2
“偏向于继承而不是继承”是一个愚蠢的想法,与“偏见于钻头”一样有意义。它们是两种适合在两种不同用例中使用的工具,实际上可以互换使用其中一种的情况实际上很少见。
梅森惠勒

2
“偏爱X而不是Y”并不意味着永远不要使用Y,只需考虑X是否会更好
Richard Tingle 2015年

2
“偏爱组成优先于继承”更准确地表示为“永不使用类继承”,并明确说明实现接口不是继承,并且如果您选择的语言仅支持抽象类,则不支持接口,并且从100%继承抽象类也可以被视为“非继承”。
David Arno

1
@MasonWheeler,没有有效的继承用例。它至少与“ goto”一样具有“邪恶”功能。
David Arno

1
@DavidArno简直太荒谬了。继承是整个编程历史上开发的最有用,最有用的功能之一。与任何有用的东西一样,有很多方法可以滥用它,但是正确使用它可以大大提高工作的能力和生产力。
梅森惠勒

Answers:


19

通过继承进行组合意味着,当您要重用或扩展现有类的功能时,通常更适合创建另一个“包装”现有类并在内部使用其实现的类。装饰器模式就是一个例子。

继承不是处理重用方案的默认方法,因为您经常只想使用基类功能的一部分,而子类不能以满足Liskov替换的方式支持基类的整个契约。原则

模板方法是适当继承的一个很好的例子。

有关在组合和继承之间进行选择的准则,请参见此答案


+1指出在部分重用一个类的情况下,使用组合比使用继承更干净地忽略未使用的部分。
劳伦斯

4

我对PHP不太熟悉,无法为您提供使用该语言的具体示例,但是这里有一些我认为有用的准则。

接口/特征对于定义许多可能没有其他共同点的类的行为很有用。

例如,如果您正在使用JSON api,则可以定义一个接口,该接口指定两个方法:“ toJson”和“ toStatusCode”。这将允许您编写一个可以使用任何实现此接口的对象的助手,并将其转换为Http响应。

在大多数情况下,这比直接继承更可取,因为它倾向于趋于浅层的层次结构,因此不太容易受到脆弱的基类问题的困扰。

最好将直接继承视为在有充分理由的情况下要做的事情,而不是除非有令人信服的理由才做的事。

在不支持接口默认实现的语言中,这可能是共享一组基本功能的一种合理方法,但通常会严重限制您对子类的操作(多重继承(如果可用)很少值得付出痛苦) 。通常,我发现编写样板重定向方法以使用composition值得痛苦。

合成应该是您的默认策略。尽可能地与接口组合,并将其作为构造函数参数传入。这将使您在编写测试时的生活更加轻松。

尽量避免使用全局单例,因为如果部分输出取决于系统时钟的状态,那么比较预期输出与实际输出是正确的选择。

当然,这并非总是可能的。例如,Play网络框架提供了一个JSON库,该库基本上是一种特定于域的语言。更改此项将是一项艰巨的任务,其中,替换对“ Json.prettyPrint”的调用将只是很小的一部分。


1

组合是当类通过实例化(可能是内部的)已经实现了该功能的类(而不是从该类继承)来提供某些功能时。

因此,例如,如果您有一个模拟船的类,并且现在被告知您的船应提供一个停机坪,那么从停机坪派生您的船是不自然的,(duh!),您应该您的飞船包含一个停机坪类,并通过某种Ship.getHelipad()方法将其公开。

在多年的历史(大约十年前)中,人们通常将继承视为聚合功能的快速简便方法,因此有许多“从直升机停机坪继承船”的例子,这些例子当然非常la脚。

但是,“继承时的偏爱”这一措辞经过了认真的措辞,以明确表明这只是一个建议,而不是一条规则。格言的作者非常谨慎,避免说“你永远不要使用继承,只能使用合成”之类的话。这基本上引起了软件工程界的注意,即继承已被过度使用,而在许多情况下,与继承相比,组合产生的设计更加清晰,美观和可维护。

因此,从本质上讲,“从继承的角度看构成合理”的格言暗示着,无论何时面对“继承还是组成?问题,您应该认真思考什么是最合适的策略,最有可能的是,最合适的策略最终将是组合而不是继承。

但是,由于这不是规则,因此您还应该记住,在许多情况下继承更为自然。如果在应该使用继承的地方使用复合,那么代码中将有很多弊端。

回到船舶示例,如果您的船舶需要提供与进行交互的接口FloatingMachine,那么从抽象FloatingMachine类派生它是很自然的事情,而抽象类又很可能又从另一个抽象Machine类派生。

这是组成与继承问题答案的经验法则:

我的课程与它需要公开的接口是否具有“是”关系?如果是,请使用继承。如果没有,请使用合成物。

船舶“是”浮动机器,而浮动机器“是”浮动机器。因此,继承对于那些人来说是完全可以的。但是,船当然不是直升机停机坪。因此,它更好地构成了停机坪的功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.