如何手写一个桥接模式?Java 代码怎么实现?
简化版
手写桥接模式的关键是先识别两个变化维度,再把其中一个维度抽成接口,另一个维度作为抽象类持有这个接口。Java 实现中通常是“抽象业务类 + 实现接口 + 多个业务子类 + 多个实现类”。
详细版
以“通知消息 × 发送渠道”为例:
- 消息类型是抽象维度:普通通知、紧急通知。
- 发送渠道是实现维度:短信、邮件。
Notification持有Sender。UrgentNotification可以增强发送内容。SmsSender、EmailSender只负责具体发送。
这种写法避免创建 UrgentSmsNotification、UrgentEmailNotification、NormalSmsNotification 等组合类。
完整版教学
一、先定义实现接口
实现接口代表底层能力。这里是发送渠道:
interface Sender {
void send(String target, String content);
}
这个接口要尽量贴近底层能力,不要把太多业务语义塞进去。比如不要设计成 sendUrgentOrderMessage(),否则实现层又会被业务类型污染。
二、编写具体实现类
class SmsSender implements Sender {
@Override
public void send(String target, String content) {
System.out.println("SMS -> " + target + ": " + content);
}
}
class EmailSender implements Sender {
@Override
public void send(String target, String content) {
System.out.println("Email -> " + target + ": " + content);
}
}
具体实现类只关注如何把内容发出去。短信有短信的网关,邮件有邮件的 SMTP 或邮件服务商,这些细节不应该进入通知类型类。
三、定义抽象部分
abstract class Notification {
protected final Sender sender;
protected Notification(Sender sender) {
this.sender = sender;
}
public abstract void notify(String target, String content);
}
Notification 就是桥接模式里的 Abstraction。它持有 Sender,但不依赖 SmsSender 或 EmailSender。
四、编写扩展抽象类
class NormalNotification extends Notification {
public NormalNotification(Sender sender) {
super(sender);
}
@Override
public void notify(String target, String content) {
sender.send(target, content);
}
}
class UrgentNotification extends Notification {
public UrgentNotification(Sender sender) {
super(sender);
}
@Override
public void notify(String target, String content) {
sender.send(target, "[紧急] " + content);
}
}
紧急通知的业务规则留在 UrgentNotification 中,发送渠道仍然只处理发送。
五、客户端组合使用
public class BridgeDemo {
public static void main(String[] args) {
Sender sms = new SmsSender();
Sender email = new EmailSender();
Notification urgentSms = new UrgentNotification(sms);
urgentSms.notify("13800000000", "订单支付失败");
Notification normalEmail = new NormalNotification(email);
normalEmail.notify("user@example.com", "欢迎注册");
}
}
这里客户端可以自由组合:
- 普通通知 + 短信
- 普通通知 + 邮件
- 紧急通知 + 短信
- 紧急通知 + 邮件
但代码上只需要 2 个通知类和 2 个发送类,而不是 4 个组合类。
六、新增能力时如何扩展
如果新增企业微信发送,只加:
class WeComSender implements Sender {
@Override
public void send(String target, String content) {
System.out.println("WeCom -> " + target + ": " + content);
}
}
如果新增验证码通知,只加:
class VerifyCodeNotification extends Notification {
public VerifyCodeNotification(Sender sender) {
super(sender);
}
@Override
public void notify(String target, String content) {
sender.send(target, "验证码:" + content + ",5分钟内有效");
}
}
两个方向互不修改,这就是桥接模式带来的扩展性。
七、实现时的注意点
桥接模式实现并不复杂,难点在于边界划分。
- 抽象类不要直接 new 具体实现类,否则仍然耦合。
- 实现接口不要暴露过多业务语义,否则维度会混在一起。
- 具体实现类不要依赖具体抽象类,否则桥会变成环。
- 如果对象创建逻辑复杂,可以配合工厂或依赖注入容器管理。
八、常见误区与追问
手写实现要验证组合关系和扩展路径,而不只是类图完整。假设消息有普通、加急2种,渠道有邮件、短信2种,桥接后只需4个具体类;增加微信渠道只新增1个 Implementor,不改两种消息类。构造时注入接口还能让单元测试替换底层发送器。
| 检查维度 | 判定依据 |
|---|---|
| 抽象侧扩展 | 新增 RefinedAbstraction |
| 实现侧扩展 | 新增 Implementor 实现 |
UrgentMessage --组合--> MessageSender <--实现-- SmsSender
易错点:桥接的关键代码是“持有接口并委托”,不是两组类碰巧同时存在。
- 误区:桥接实现必须使用继承。 抽象侧与实现侧的连接依赖组合,双方内部是否继承并非核心。
- 追问:Implementor 应由谁选择? 工厂、依赖注入容器或客户端装配代码都可以根据配置选择。
- 误区:每个 RefinedAbstraction 都要绑定固定实现。 运行期可注入不同 Implementor,这正是组合带来的灵活性。
- 追问:如何测试委托是否正确? 注入假 Implementor,断言高层方法传入了正确参数和调用次数。
- 追问:实现类增多怎么管理? 按两个维度分别组织包和注册表,避免重新形成笛卡尔积命名。
九、加强记忆
手写桥接模式时按“先拆维度,再抽接口,最后组合”的顺序:一个维度做抽象业务类,另一个维度做实现接口,抽象对象持有实现接口,客户端负责把两边拼起来。