Composition(组合) 通过持有其他对象("has-a"关系)来构建行为,并将任务委派给它们。Inheritance(继承) 通过扩展一个类("is-a")来获取行为。"偏好组合"的建议存在是因为继承会创建对基类的紧密、脆弱的耦合,而组合保持灵活性。
继承在代码复用中的问题
java
<T> <T> {
{ add(x); }
T { remove(size() - ); }
}
class Stack<T> {
private final List<T> items = new ArrayList<>(); // HAS-A list
void push(T x) { items.add(x); }
T pop() { return items.remove(items.size() - 1); }
// only push/pop are exposed → LIFO invariant is safe
}
通过持有一个列表而不是成为一个列表,Stack只暴露它想要的操作。
| 组合 | 继承 |
|---|---|
| 灵活,可在运行时交换部件 | 在编译时固定 |
| 隐藏内部细节(委派) | 继承整个接口 |
| 更多接线/样板代码 | 当确实是 is-a 时代码更少 |
| 避免脆弱的基类问题 | 基类改变时子类易损坏 |
仅在真正的、稳定的 is-a 关系中使用继承;在"我需要这个行为"的情况下使用组合。
继承暴露了父类的整个接口,所以子类无法完全控制自己的契约——Stack extends ArrayList泄漏是经典例子。
组合使关系保持显式和可交换,这就是为什么依赖注入、策略模式和大多数灵活的设计都依赖于它。