是什么
以委托替换继承(Replace Superclass with Delegate)处理的是只为复用实现而建立的继承关系:让子类改为持有父类的一个实例,把真正需要的操作转发过去。这样类型语义不再被伪造,继承带来的多余接口也随之消失,替换被委托对象时也更自由。它是组合优先这一原则在重构层面的具体落法。
信号与做法
| 信号 | 继承只是为了复用代码,父类的部分方法对子类并不适用 |
|---|---|
| 做法 | 子类持有父类实例,按白名单定向转发需要的能力 |
| 收益 | 公开接口收窄,实现替换更自由,类型语义与用法一致 |
| 对照 | 若类型语义成立则保留继承,例如确实存在是一种的关系 |
典型使用场景
- 容器类继承集合类:只需栈或队列语义,却被动继承了整套随机访问接口
- 工具基类被当成代码复用手段:各子类只用到基类一两个方法
- 框架基类携带大量可选钩子:用组合后只实现真正必要的能力
- 需要替换底层实现:委托对象的类型可以独立变化,继承层级做不到
- 测试替身难写:继承层级深导致构造复杂,改为委托后可以只替换被委托方
改造示例
改造示例
// 改造前:Stack 继承 Vector,只为了复用底层存储
class Stack extends Vector { }
// 改造后:持有委托对象,只转发栈需要的操作
class Stack {
private final Deque<Object> elements = new ArrayDeque<>();
void push(Object o) { elements.push(o); }
Object pop() { return elements.pop(); }
Object peek() { return elements.peek(); }
}改造步骤与检查
| 步骤 | 动作 | 检查点 |
|---|---|---|
| 一 | 在子类中加入父类字段并初始化 | 构造参数变化是否影响所有调用点 |
| 二 | 把子类方法改为转发调用 | 只转发实际需要的操作,不照搬全部 |
| 三 | 解除继承关系 | 类型检查与多态调用处是否仍成立 |
| 四 | 替换外部引用 | 外部是否按父类类型引用了子类实例 |
| 五 | 补齐隐含契约 | 相等性、哈希与克隆语义是否被改变 |
转发采用白名单方式:只暴露确实需要的能力,而不是机械地把父类所有公开方法都转发一遍。后者会让新类型的接口宽度与父类相同,暴露面问题并未解决。
关闭继承关系时留意父类的隐含契约。原先继承得到的相等性、哈希值、字符串表示与克隆行为在委托形态下不再自动具备,若调用方曾依赖其中任何一项,需要显式实现。
落地检查清单
- 确认原继承关系不是真实的类型语义关系
- 转发集合经过筛选,接口宽度明显收窄
- 外部引用点全部改为新类型,编译通过
- 补上必要的行为契约与相关测试
- 类文档说明被委托对象的替换方式