桥接模式(Bridge):将抽象与实现分离,让它们各自独立演化
桥接模式,说白了就是「把抽象和实现拆开,各玩各的」。
我刚开始接触这个模式时,觉得它跟策略模式有点像。后来在项目中踩过坑才明白——桥接模式解决的是「多维度的变化」问题。你想想看,如果一个类既要应对不同的抽象层次,又要应对不同的实现方式,直接继承下去会变成什么样?
嗯,那就是传说中的「类爆炸」。
为什么需要桥接模式?
假设你要开发一个消息推送系统。消息可以分「普通消息」「紧急消息」「加急消息」。推送方式有「短信」「邮件」「App推送」。
如果不用桥接模式,你可能会这样设计:
class NormalSmsMessage { ... }
class NormalEmailMessage { ... }
class NormalAppMessage { ... }
class UrgentSmsMessage { ... }
class UrgentEmailMessage { ... }
// ... 继续写下去,你会疯掉
3种消息类型 × 3种推送方式 = 9个类。如果再加一种消息类型或推送方式,类数量会成倍增长。
核心问题:抽象(消息类型)和实现(推送方式)在同一个继承体系中耦合在一起,导致变化互相影响。
桥接模式的核心思想
桥接模式的做法很简单:
- 把「抽象部分」定义为一个抽象类或接口
- 把「实现部分」定义为另一个接口
- 抽象类中持有实现接口的引用
- 两者通过「组合」的方式连接起来
这样,抽象和实现就可以各自独立变化,互不干扰。
结构图
下面这张图展示了桥接模式的核心结构:
代码示例:消息推送系统
先定义实现部分的接口:
// 实现接口:推送方式
public interface MessageSender {
void send(String message);
}
然后是实现类的具体实现:
public class SmsSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过短信发送:" + message);
}
}
public class EmailSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过邮件发送:" + message);
}
}
public class AppPushSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("通过App推送发送:" + message);
}
}
接下来是抽象部分:
// 抽象类:消息
public abstract class Message {
protected MessageSender sender;
public Message(MessageSender sender) {
this.sender = sender;
}
public abstract void send(String content);
}
精化抽象类:
public class NormalMessage extends Message {
public NormalMessage(MessageSender sender) {
super(sender);
}
@Override
public void send(String content) {
sender.send("[普通]" + content);
}
}
public class UrgentMessage extends Message {
public UrgentMessage(MessageSender sender) {
super(sender);
}
@Override
public void send(String content) {
sender.send("[紧急]" + content);
}
}
public class CriticalMessage extends Message {
public CriticalMessage(MessageSender sender) {
super(sender);
}
@Override
public void send(String content) {
sender.send("[加急]" + content);
}
}
客户端使用:
public class Client {
public static void main(String[] args) {
// 普通消息 + 短信
Message msg1 = new NormalMessage(new SmsSender());
msg1.send("系统维护通知");
// 紧急消息 + 邮件
Message msg2 = new UrgentMessage(new EmailSender());
msg2.send("服务器宕机");
// 加急消息 + App推送
Message msg3 = new CriticalMessage(new AppPushSender());
msg3.send("数据库连接失败");
}
}
桥接模式 vs 策略模式
我经常被问到这个问题。两者确实很像,但关注点不同:
| 对比维度 | 桥接模式 | 策略模式 |
|---|---|---|
| 关注点 | 抽象与实现的分离 | 算法的替换 |
| 变化维度 | 两个维度独立变化 | 一个维度(算法)变化 |
| 结构 | 抽象类持有实现接口 | 上下文类持有策略接口 |
| 典型场景 | 跨平台UI、多数据库驱动 | 排序算法选择、支付方式选择 |
我的经验:如果你发现一个类有两个独立变化的维度,而且用继承会导致类数量爆炸,那桥接模式就是你的解药。我在做支付网关时,支付渠道(微信/支付宝/银联)和支付方式(扫码/跳转/小程序)就是两个独立维度,用桥接模式后代码清爽多了。
适用场景
- 一个类存在两个独立变化的维度
- 不希望抽象和实现之间有固定的绑定关系
- 抽象和实现都需要可扩展
- 对具体实现的修改不影响客户端代码
优缺点
优点:
- 抽象和实现分离,各自独立扩展
- 符合开闭原则,新增抽象或实现都不修改现有代码
- 实现细节对抽象透明
缺点:
- 增加了系统的复杂度
- 需要正确识别出两个独立变化的维度
- 如果维度不多,直接用继承反而更简单
避坑指南:我曾经在一个项目中过度使用桥接模式,把本来只有一个变化维度的功能硬拆成两个维度,结果代码变得又绕又难懂。记住,桥接模式是为「多维变化」准备的,不要为了用模式而用模式。
实际应用案例
桥接模式在现实框架中随处可见:
- JDBC驱动:JDBC接口是抽象,不同数据库的驱动是实现
- Java AWT:Component是抽象,Peer是实现(不同操作系统)
- 日志框架:Logger是抽象,Appender是实现(文件/控制台/网络)
- UI框架:窗口是抽象,渲染引擎是实现(Windows/Mac/Linux)
说白了,桥接模式就是「组合优于继承」的经典体现。它告诉我们:当变化来自多个方向时,不要试图用继承来搞定一切,组合才是更优雅的答案。
我个人习惯在设计初期先问自己一个问题:「这个类未来会在哪些维度上变化?」如果答案是两个以上,桥接模式就该出场了。
公众号:蓝海资料掘金营,微信deep3321