关于Java:偏好合成与继承

Favor composition over inheritance

本问题已经有最佳答案,请猛点这里访问。

Favor composition over inheritance

是非常流行的短语。我读了几篇文章,最后每篇文章都说

use inheritance when there is pure IS-A relationship between classes.

本文的一个示例:

苹果和水果之间存在着明显的"是"关系,即苹果是一种水果,但作者也将其表示为"苹果有"——一种水果(成分),用继承来实现时会显示出陷阱。

我在这里有些困惑,陈述的意思是什么

use inheritance when there is pure IS-A relationship between classes.

在继承上使用组合是否意味着即使存在纯IS-A关系,也要始终尝试应用组合,并且只在组合没有意义的情况下保留继承?


当您使用继承重用来自超类的代码,而不是重写方法和定义另一个多态行为时,通常会指示您应该使用组合而不是继承。

java.util.Properties类是继承使用不当的一个很好的例子。它不是使用哈希表来存储其属性,而是扩展哈希表,以便重用其方法,并避免使用委托重新实现其中的一些方法。


我认为这是面向对象设计中讨论最多的一点。正如本文所建议的,组合总是优先于继承。这并不意味着你不应该使用继承。你应该去更有意义的地方(有争议的地方)。

使用合成有很多优点,其中有两个优点:

  • 您将完全控制您的实现。也就是说,您只能公开您打算公开的方法。
  • 超级类中的任何更改都可以通过只在类中修改来屏蔽。任何使用类的客户机类都不需要进行修改。
  • 允许您控制何时加载超级类(延迟加载)


我想一个好的指导方针是:

When there is an IS-A relationship, use inheritance. Otherwise, use
composition.

这与面向对象设计中的另一个概念——多态性有关。多态性是许多OOP语言的一个特性,只要第一个类是第二个类的子类,就可以用一个对象代替另一个对象。

举例来说,想象一下,如果你有一个功能,接受一种动物。使用继承时,只有一个函数:

1
void feed( Animal a );

多态性保证了我们放入的动物的任何亚类都将被接受。否则,我们将被迫为每种类型编写一个函数。我认为这样做的好处大于它的缺点(例如减少封装)。

如果没有IS-A关系,我认为多态性不会非常有效。因此,最好使用组合并增强类之间的封装/隔离。


另外,我想补充一点,装饰器模式是一个示例,您可以在继承上使用组合,并动态地向对象添加职责。在"有效继承比继承"项目中,在有效Java中提到了装饰器模式的例子。从Java API中获取一个例子;Buffer阅读器装入阅读器(而不是扩展FieleRader、PiPadReader、FieldReader等),因此有能力对任何类型的阅读器进行装饰。缓冲功能在运行时附加到这些读卡器上,这就是装饰器模式。

在这里,通过子类化进行扩展是不切实际的。


在本文中,没有这样的短语:

use inheritance when there is pure IS-A relationship between classes

此外,除了你的帖子,谷歌在其他地方没有找到它。

相反,文章写道:

Make sure inheritance models the is-a relationship.
My main guiding philosophy is that inheritance should be used only when a subclass is-a superclass. In the example above, an Apple likely is-a Fruit, so I would be inclined to use inheritance.

这意味着,如果存在关系,请首先尝试使用继承,而不是组合。而且,using composition over inheritance没有任何偏好——每个都有利于自己的角色。


当关系是永久的时使用继承。不要仅仅为了获得自由行为而使用扩展。这就是他们在IS上所指的——一个例子。只有当扩展类确实是父类的扩展时才进行扩展。有时它不会被切割和干燥,但一般来说。如果您需要从"正确的"类进行扩展,那么组合在将来也提供了灵活性。

这是一篇关于延伸的古老而伟大的文章:

http://www.javaworld.com/javaworld/jw-08-2003/jw-0801-toolbox.html


我会说不,在水果/苹果的场景中,使用继承是很有意义的。这篇文章的作者也证实了这一点:"在上面的例子中,苹果很可能是一种水果,所以我倾向于使用继承。"

如果您的子类显然是一个超类,那么继承就可以了。在实践中,你会发现更多的情况下,更好地解决使用组合。