桥接模式相比继承有什么优势?
简化版
桥接模式相比继承的优势是降低耦合、减少组合类、提升扩展性。继承会把多个变化维度固化在类层级里,桥接用组合让不同维度在运行时连接,更灵活也更容易维护。
详细版
继承适合表达稳定的“is-a”关系,但不适合表达多个维度的自由组合。
例如:
Circle是一种Shape,这种继承关系相对自然。SvgCircle既代表 SVG 又代表 Circle,把两个维度压在一个类名里,扩展时会膨胀。
桥接模式把一个维度放在抽象层,另一个维度放在实现接口里。新增实现方式不需要改抽象层,新增抽象类型也不需要改实现层。
完整版教学
一、继承的优势和边界
继承并不是坏设计。它适合表达清晰、稳定、层次不深的类型关系。
例如:
Shape
├── Circle
└── Rectangle
如果系统只关心图形类型,这样写很直接。
但当图形还要支持多种渲染方式时,继续用继承表达组合就会变重。继承层级一旦承载太多维度,类就会越来越细碎。
二、继承方案的主要问题
继承表达多维度组合时有几个典型问题:
- 子类数量按维度相乘增长。
- 父类修改可能影响大量子类。
- 相同实现逻辑容易散落在多个组合类。
- 运行时切换实现方式很麻烦。
- 类层级过深后理解成本上升。
例如 PdfFinanceReport 如果继承了 FinanceReport,那它既承载财务口径,又承载 PDF 输出。以后要把同一份财务报表导出 Excel,就要再写一个 ExcelFinanceReport。
三、桥接方案如何改善
桥接方案会把报表口径和导出格式拆开:
abstract class Report {
protected final Exporter exporter;
protected Report(Exporter exporter) {
this.exporter = exporter;
}
public abstract void export();
}
报表类负责准备数据,导出器负责输出格式。两边职责清楚,扩展也分开。
四、组合带来的运行时灵活性
继承是在编译期固定类型层级,组合是在对象创建时建立关系。
这意味着桥接模式可以运行时决定使用哪种实现:
Exporter exporter = userChoosePdf ? new PdfExporter() : new ExcelExporter();
Report report = new FinanceReport(exporter);
report.export();
如果用继承,就要提前选择 PdfFinanceReport 或 ExcelFinanceReport,并且相关逻辑更容易散在不同类中。
五、桥接不是完全替代继承
桥接模式内部仍然可以使用继承。抽象侧可能有父类和子类,实现侧也可能有接口和多个实现类。
它反对的不是继承本身,而是用一条继承链同时表达多个变化维度。
更准确地说,桥接模式是把继承从“组合维度”中解放出来,让每个维度内部各自组织层次。
六、什么时候继承更简单
如果需求非常稳定,并且只有少量固定类型,用桥接可能没有必要。
例如一个小工具只有 PdfReport 和 ExcelReport 两个类,没有报表类型扩展,也没有运行时组合需求,直接写两个类更清楚。
设计模式要看变化趋势。如果变化维度不成立,桥接会增加额外抽象。
七、常见误区与追问
继承方案把两个维度固化在类层次中,桥接则把其中一维变成对象组合。3种形状配4种渲染器若交叉继承,理论上要12个组合子类;桥接只需3个形状类加4个渲染实现,共7个核心类型。数量优势之外,运行期替换实现也是继承难以提供的。
| 检查维度 | 判定依据 |
|---|---|
| 交叉继承 | 组合数量约为 A × B |
| 桥接拆分 | 核心类型数量约为 A + B |
3 × 4 = 12;桥接后 3 + 4 = 7
记忆钩子:继承把选择写进类型,桥接把选择放进对象装配。
- 误区:继承永远不如桥接。 单一稳定维度且共享实现明确时,继承更直接。
- 追问:桥接如何避免类爆炸? 两个维度分别扩展,通过运行期组合形成搭配,不为每个搭配建子类。
- 误区:桥接会消除所有组合数量。 运行期组合仍有 A×B 种,只是不需要 A×B 个类。
- 追问:何时从继承重构为桥接? 第二个变化维度出现、子类命名交叉且修改传播时。
- 追问:组合的代价是什么? 多一次对象间委托,并需要明确装配与生命周期管理。
八、加强记忆
桥接和继承的区别可以记成:继承把组合写死在类名里,桥接把组合放到对象关系里。前者适合稳定层级,后者适合多维度变化。