桥接模式中的抽象和实现分别指什么?
简化版
桥接模式里的抽象和实现是设计维度,不是单纯的语法概念。抽象指面向业务的高层接口和行为,实现指支撑这些行为的底层能力,两者通过组合连接,从而独立演进。
详细版
很多人误以为桥接模式中的“抽象”就是 abstract class,“实现”就是 implements interface。这只是一种常见代码表现,不是它的真正含义。
在桥接模式里:
- 抽象部分回答“业务上要做什么”。
- 实现部分回答“底层用什么方式做”。
例如消息系统中:
- 抽象:普通消息、紧急消息、验证码消息。
- 实现:短信发送、邮件发送、企业微信发送。
业务消息不应该继承具体发送渠道,而应该持有发送渠道接口。这样业务语义和底层通道就可以分别变化。
完整版教学
一、抽象不是等于抽象类
桥接模式中的 Abstraction 是业务侧高层概念。它通常会被写成抽象类,是因为它需要持有实现接口,并为子类提供公共逻辑。
但从设计上看,它的重点不是“不能实例化”,而是“代表业务使用者理解的对象”。
例如:
Report:报表。Notification:通知。Shape:图形。RemoteControl:遥控器。
这些都是业务语义很强的抽象。用户更关心“发送通知”“绘制图形”“导出报表”,而不是底层协议和平台细节。
二、实现不是等于实现类
桥接模式中的 Implementor 是底层能力接口。它通常写成接口,是因为抽象层只需要依赖能力契约,不需要依赖具体实现。
例如:
Sender:发送能力。Renderer:绘制能力。Exporter:导出能力。Device:设备控制能力。
这些接口不一定代表完整业务对象,它们更像可替换的能力插槽。
三、为什么要区分这两个概念
如果不区分抽象和实现,就容易把所有变化混到一个继承体系里。
假设报表系统有两个变化方向:
- 报表类型:日报、月报、财务报表。
- 导出格式:PDF、Excel、HTML。
如果把两者混在一起,类名会变成 DailyPdfReport、MonthlyExcelReport、FinanceHtmlReport。这些类名看起来清晰,但一旦组合变多,维护会很沉。
桥接模式会把它拆成:
- 抽象侧:
DailyReport、MonthlyReport、FinanceReport。 - 实现侧:
PdfExporter、ExcelExporter、HtmlExporter。
报表类型负责组织业务数据,导出器负责输出格式。
四、抽象侧和实现侧的职责边界
划分边界时可以按这几个问题判断:
- 这个逻辑是否代表业务规则?如果是,放在抽象侧。
- 这个逻辑是否代表技术实现或底层能力?如果是,放在实现侧。
- 这个变化是否会和另一个变化自由组合?如果会,要考虑桥接。
例如紧急通知是否要加前缀、是否升级提醒等级,这是抽象侧业务规则;短信如何调用网关、邮件如何拼 MIME,这是实现侧技术细节。
五、桥接模式中的“桥”到底是什么
桥不是一个单独的类名,也不是必须叫 Bridge。它通常就是抽象类里保存的实现接口引用。
abstract class Shape {
protected Renderer renderer;
}
这里 renderer 就是桥。Shape 通过它把绘制工作交给不同平台的渲染器。
六、容易答错的点
面试中常见错误是把桥接模式解释成“一个接口多个实现”。这太宽泛了,策略模式、适配器模式、工厂模式也经常有接口和实现。
桥接模式的特征必须包括:
- 至少两个独立变化维度。
- 抽象侧持有实现侧接口。
- 两个维度都可以扩展。
- 目的是避免继承组合爆炸。
只说接口和实现,不足以说明桥接模式。
七、常见误区与追问
桥接语境里的 Abstraction 和 Implementor 是两个业务变化轴,不是“抽象类”和“具体代码”的字面翻译。以2种消息类型和3种发送渠道为例,前者决定消息语义,后者完成渠道发送;两侧通过组合连接。若 Abstraction 又直接判断渠道类型,独立演化的边界就被破坏了。
| 检查维度 | 判定依据 |
|---|---|
| Abstraction | 面向客户端,表达高层业务语义 |
| Implementor | 提供可替换的底层能力接口 |
客户端 -> RefinedAbstraction -> Implementor -> 具体能力
记忆钩子:上层决定“做什么业务”,实现侧决定“靠什么能力完成”。
- 误区:Abstraction 必须写成 Java abstract class。 它是模式角色,可以是普通类或接口,关键是持有 Implementor 抽象。
- 追问:Implementor 为什么不直接面向客户端? 它通常粒度更底层,由 Abstraction 组合成客户端需要的业务动作。
- 误区:两侧接口必须一一对应。 Abstraction 可以把多个底层操作编排成一个高层用例。
- 追问:依赖方向应该怎样? 高层抽象依赖 Implementor 接口,具体实现不应反向依赖某个 RefinedAbstraction。
- 追问:怎样验证两个维度真的独立? 分别新增第3种抽象和第4种实现,确认另一侧既有类型无需修改。
八、加强记忆
桥接模式里的抽象和实现要按“业务语义”和“底层能力”来记:业务侧管要做什么,实现侧管怎么落地,中间靠接口引用连接,两边各走各的扩展路线。