使用组合而不是继承的一个主要原因是继承会导致更高的耦合。那个怎么样? 只要我将基类字段和方法设置为
private
,我就不会向子类公开任何实现细节,对吧?那么对基类的更改不会影响我的子类吗?对于公共成员,我认为组成没有任何区别。在这两种情况下,它们都会暴露给子类(在继承的情况下)或包装类(在组合的情况下)。
任何人都可以给我举一个例子,当成员设置为私有/公共时,继承会导致比组合更高的耦合吗?
我没有向子类公开任何实现细节,对吗?
你是。事实上,您的
Dog
类扩展了 Animal
本身暴露了实现细节。
想象以下 2 个非常相似的相同想法:一个代表狗窝的类。
class Kennel implements Iterable<Dog> {
private final List<Dog> dogs = new ArrayList<Dog>();
public int size() { return dogs.size(); }
public void addDog(Dog dog) { dogs.add(dog); }
@Override public Iterator<Dog> iterator() { return dogs.iterator(); }
}
class Kennel extends ArrayList<Dog> {}
是的,take 2 的一个优点是代码少得多。但是,take 2 将对 API 的大量控制权交给了 ArrayList 的作者,并限制了未来的任何更改。
例如:
在第 2 步中,如果您后来决定改用
ArrayDeque
,则实际上无法这样做。某些代码可能已编写为 ArrayList<Dog> x = new Kennel();
,当您进行更改时,该代码现在将不再编译。当您更改 API 时,可能存在曾经可以工作但现在不再编译/工作的代码,这称为“向后不兼容的更改”,并且您无法在不增加主版本号的情况下发布该更改,而不是其他东西你可能想做的事。相反,在步骤 1 中,您可以这样做。您的代码的任何用户都不会知道它。
,并且你无法挑选。例如,您的 Kennel 类现在也将具有 ensureCapacity
方法。即使您要求 Kennel 的用户忽略继承链,除了非常有限的一组列表之外,该方法也没有任何意义。您现在已经将您的 Kennel 类紧密地绑定到这样的工作中。例如,如果您将其替换为 LinkedList,其中
ensureCapacity
根本上没有任何意义,那么您或多或少会被迫这样做:
class Kennel extends LinkedList<Dog> { // used to be ArrayList
public void ensureCapacity(int cap) {
// do nothing - linkedlists don't work like that
}
}
除了更改继承之外,“不会破坏任何东西”——任何已经存在的称为
ensureCapacity
(毕竟,这是
public
类上的 Kennel
方法)的东西或多或少都会继续工作。但必须添加和维护这种因历史原因而保留的无所事事的方法实在是太奇怪了。相比之下,采取1也没有遇到这个问题。
那么教训是什么?
并非如此。
实际的教训是:继承
必须具有语义意义。请记住,如果您写 class Foo extends Bar
,您是在说:Foo 的所有实例也是 Bar 的实例。只是,对这个想法有更具体的看法。
all Kennels are also ArrayList<Dog> objects
就是在推动它。这不是明显的事实。
对比class Dog extends Animal
。这就是说:所有的狗也是动物;只是对这个想法有更具体的看法。这确实是事实,而且写起来很明智。
抛开狗和动物的陈旧的栗子,class ArrayList extends AbstractList
实际上是同一件事:是的,所有数组列表都是抽象列表。是的,现在这已经被永久硬编码到 ArrayList 的规范中了,这没什么问题。如果不是的话那就奇怪了。
这也许是最简单的方法:你能想象Kennel
作为
ArrayList<Dog>
的子类而合理地存在吗?如果可以的话,这非常强烈地表明它不应该是这样。我无法想象 Dog
会合理地存在,因为 not是
Animal
的子类型。这根本没有道理。狗是动物。这是您正在建模的事物固有的。这不是一个实现细节 - 这是这个概念的细节,该代码只是其软件表示。为了确定某件事是否具有语义意义,将任何事情付诸实施总是错误的。即使狗舍的千公里概览设计看起来与List<Dog>
非常相似,也没关系。重要的是“‘狗舍’这个概念的语义概念是否本质上是‘这是对‘列表’概念的更具体的理解”。事实并非如此。因此,class Kennel extends ArrayList<Dog>
,虽然最初可能很方便,但没有必要使用正确编写的 API。