设计模式:桥接模式

欢迎你来读这篇博客,这篇博客主要是关于桥接模式
其中包括桥接模式的核心思想、适用场景、优缺点、与适配器模式和策略模式的区别,以及 Java 后端开发中消息通知系统的完整案例。

序言

在软件开发中,有一种很典型的复杂度来源:

一个业务对象存在多个变化维度。

比如消息通知系统。

消息类型可能有:

  • 普通消息;
  • 加急消息;
  • 验证码消息;
  • 营销消息。

发送渠道可能有:

  • 邮件;
  • 短信;
  • 企业微信;
  • 钉钉。

如果直接用继承来建模,很容易写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
EmailNormalMessage
SmsNormalMessage
WeChatNormalMessage
DingTalkNormalMessage

EmailUrgentMessage
SmsUrgentMessage
WeChatUrgentMessage
DingTalkUrgentMessage

EmailCodeMessage
SmsCodeMessage
WeChatCodeMessage
DingTalkCodeMessage

EmailMarketingMessage
SmsMarketingMessage
WeChatMarketingMessage
DingTalkMarketingMessage

光是 4 种消息类型和 4 种发送渠道,就有 16 个类。

如果再新增一种消息类型,所有渠道都要配一遍。

如果再新增一种发送渠道,所有消息类型也要配一遍。

这就是典型的类爆炸。

桥接模式就是为了解决这类问题:

当一个类存在多个独立变化维度时,不要把它们全部塞进继承层级,而是把抽象部分和实现部分拆开,让它们可以独立变化。

说得直白一点:

桥接模式就是把“是什么”和“怎么做”拆开,中间用组合关系搭一座桥。

正文

chapter 1:什么是桥接模式

桥接模式,英文是 Bridge Pattern,属于结构型设计模式。

它的定义是:

将抽象部分与实现部分分离,使它们都可以独立变化。

这里的“抽象部分”和“实现部分”不是简单指接口和实现类,而是指系统中的两个变化维度。

比如消息通知系统里:

  • 抽象部分:消息类型,例如普通消息、加急消息、验证码消息;
  • 实现部分:发送渠道,例如邮件、短信、企业微信、钉钉。

如果用继承强行组合这两个维度,就会类爆炸。

如果用桥接模式,就可以拆成两个独立层次:

1
2
消息类型维度:NormalMessage、UrgentMessage、VerifyCodeMessage
发送渠道维度:EmailSender、SmsSender、WeChatSender

消息类型内部持有发送渠道接口:

1
protected MessageSender messageSender;

这样消息类型和发送渠道就通过组合连接起来。

这座“组合之桥”,就是桥接模式的核心。

chapter 2:为什么需要桥接模式

如果一个系统只有一个变化维度,继承通常还能接受。

比如:

1
2
3
4
Message
├── NormalMessage
├── UrgentMessage
└── VerifyCodeMessage

但如果出现第二个变化维度,就容易失控。

比如消息还要区分发送渠道:

1
2
3
4
5
6
7
8
9
10
Message
├── EmailNormalMessage
├── SmsNormalMessage
├── WeChatNormalMessage
├── EmailUrgentMessage
├── SmsUrgentMessage
├── WeChatUrgentMessage
├── EmailVerifyCodeMessage
├── SmsVerifyCodeMessage
└── WeChatVerifyCodeMessage

如果有 m 种消息类型,n 种发送渠道,类数量可能变成:

1
m × n

这不是设计模式,是排列组合开始上班。

桥接模式的思路是:

不要让两个维度在继承体系里相乘,而是让它们在运行时组合。

拆开之后,类数量变成:

1
m + n

例如:

1
2
3
消息类型:3 个类
发送渠道:3 个类
总共:6 个类

而不是 9 个组合类。

当维度更多时,差距会更明显。

chapter 3:桥接模式的核心结构

桥接模式通常包含四个角色:

  1. Abstraction 抽象类:定义抽象部分的接口,并持有实现部分接口。
  2. RefinedAbstraction 扩展抽象类:扩展抽象部分的具体行为。
  3. Implementor 实现接口:定义实现部分的接口。
  4. ConcreteImplementor 具体实现类:实现具体实现部分。

结构如下:

1
2
3
4
5
6
7
Client


Abstraction ───────► Implementor
▲ ▲
│ │
RefinedAbstraction ConcreteImplementor

在消息通知案例中:

1
2
3
4
5
Abstraction:Message
RefinedAbstraction:NormalMessage、UrgentMessage、VerifyCodeMessage

Implementor:MessageSender
ConcreteImplementor:EmailSender、SmsSender、WeChatSender

Message 不直接负责发送细节,而是把发送动作委托给 MessageSender

这就是“抽象”和“实现”的分离。

chapter 4:桥接模式解决的是多维度变化

桥接模式最关键的识别点是:

系统中存在两个或多个可以独立变化的维度。

例如:

1. 消息类型 × 发送渠道

  • 普通消息、加急消息、验证码消息;
  • 邮件、短信、微信、钉钉。

2. 文件类型 × 存储方式

  • 图片、视频、文档;
  • 本地存储、OSS、COS、MinIO。

3. 支付业务 × 支付渠道

  • 普通支付、分账支付、退款、预授权;
  • 支付宝、微信、Stripe、PayPal。

4. 报表类型 × 导出格式

  • 订单报表、支付报表、用户报表;
  • Excel、CSV、PDF、JSON。

5. 日志类型 × 输出目标

  • 业务日志、审计日志、监控日志;
  • 控制台、文件、Kafka、ELK。

如果这些维度被写进同一个继承体系,类会迅速膨胀。

桥接模式的目标就是拆维度。

chapter 5:不用桥接模式会怎样

假设我们做一个消息通知系统。

最初只有邮件通知。

1
2
3
4
public abstract class Message {

public abstract void send(String receiver, String content);
}

普通邮件消息:

1
2
3
4
5
6
7
public class EmailNormalMessage extends Message {

@Override
public void send(String receiver, String content) {
System.out.println("发送普通邮件给 " + receiver + ",内容:" + content);
}
}

加急邮件消息:

1
2
3
4
5
6
7
public class EmailUrgentMessage extends Message {

@Override
public void send(String receiver, String content) {
System.out.println("发送加急邮件给 " + receiver + ",内容:[加急]" + content);
}
}

后来新增短信渠道。

1
2
3
4
5
6
7
public class SmsNormalMessage extends Message {

@Override
public void send(String receiver, String content) {
System.out.println("发送普通短信给 " + receiver + ",内容:" + content);
}
}
1
2
3
4
5
6
7
public class SmsUrgentMessage extends Message {

@Override
public void send(String receiver, String content) {
System.out.println("发送加急短信给 " + receiver + ",内容:[加急]" + content);
}
}

再后来新增企业微信。

1
2
public class WeChatNormalMessage extends Message {
}
1
2
public class WeChatUrgentMessage extends Message {
}

这时问题已经出现了。

如果有 4 种消息类型和 5 种发送渠道,就要 20 个类。

如果未来还有消息模板、国际化、重试策略、限流策略,那就更乱。

这说明一个问题:

当前继承体系把两个独立变化维度绑死了。

桥接模式要做的,就是把它们拆开。

chapter 6:案例背景:消息通知系统

现在我们重新设计消息通知系统。

需求如下:

  1. 支持多种消息类型;
  2. 支持多种发送渠道;
  3. 消息类型和发送渠道可以自由组合;
  4. 新增消息类型时,不修改发送渠道;
  5. 新增发送渠道时,不修改消息类型;
  6. 业务层尽量依赖抽象接口。

消息类型包括:

  • 普通消息;
  • 加急消息;
  • 验证码消息。

发送渠道包括:

  • 邮件;
  • 短信;
  • 企业微信。

使用桥接模式后,我们把系统拆成两个维度:

1
2
消息抽象维度:Message
发送实现维度:MessageSender

Message 持有 MessageSender

1
2
3
4
5
6
7
8
9
10
public abstract class Message {

protected final MessageSender messageSender;

protected Message(MessageSender messageSender) {
this.messageSender = messageSender;
}

public abstract void send(String receiver, String content);
}

chapter 7:定义实现部分接口:MessageSender

先定义发送渠道接口。

1
2
3
4
public interface MessageSender {

void send(String receiver, String content);
}

这个接口代表桥接模式中的 Implementor

它只关心一件事:

如何把消息发出去。

至于这条消息是普通消息、加急消息还是验证码消息,它不关心。

chapter 8:定义具体发送渠道

1. 邮件发送器

1
2
3
4
5
6
7
public class EmailMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过邮件发送给 " + receiver + ",内容:" + content);
}
}

2. 短信发送器

1
2
3
4
5
6
7
public class SmsMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过短信发送给 " + receiver + ",内容:" + content);
}
}

3. 企业微信发送器

1
2
3
4
5
6
7
public class WeChatMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过企业微信发送给 " + receiver + ",内容:" + content);
}
}

这几个类是桥接模式中的具体实现类。

它们只处理发送方式的差异。

chapter 9:定义抽象部分:Message

然后定义消息抽象类。

1
2
3
4
5
6
7
8
9
10
public abstract class Message {

protected final MessageSender messageSender;

protected Message(MessageSender messageSender) {
this.messageSender = messageSender;
}

public abstract void send(String receiver, String content);
}

这里的关键点是:

1
protected final MessageSender messageSender;

消息类不直接依赖邮件、短信、企业微信这些具体发送器,而是依赖发送接口。

消息类型通过组合持有发送实现。

这就是桥接模式里的“桥”。

chapter 10:定义具体消息类型

1. 普通消息

1
2
3
4
5
6
7
8
9
10
11
public class NormalMessage extends Message {

public NormalMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
messageSender.send(receiver, content);
}
}

普通消息不做特殊处理,直接发送。

2. 加急消息

1
2
3
4
5
6
7
8
9
10
11
12
13
public class UrgentMessage extends Message {

public UrgentMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
String urgentContent = "[加急] " + content;

messageSender.send(receiver, urgentContent);
}
}

加急消息在内容前面加上 [加急]

以后还可以在这里加:

  • 优先级;
  • 重试;
  • 告警升级;
  • 多渠道补偿;
  • 发送记录标记。

3. 验证码消息

1
2
3
4
5
6
7
8
9
10
11
12
13
public class VerifyCodeMessage extends Message {

public VerifyCodeMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
String codeContent = "您的验证码是:" + content + ",5 分钟内有效。";

messageSender.send(receiver, codeContent);
}
}

验证码消息负责处理验证码内容格式。

发送渠道仍然由 MessageSender 决定。

chapter 11:客户端使用

现在可以自由组合消息类型和发送渠道。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {

public static void main(String[] args) {
MessageSender emailSender = new EmailMessageSender();
MessageSender smsSender = new SmsMessageSender();
MessageSender weChatSender = new WeChatMessageSender();

Message normalEmailMessage = new NormalMessage(emailSender);
normalEmailMessage.send("user@example.com", "欢迎注册系统");

Message urgentSmsMessage = new UrgentMessage(smsSender);
urgentSmsMessage.send("13800000000", "您的订单支付异常,请及时处理");

Message verifyCodeWeChatMessage = new VerifyCodeMessage(weChatSender);
verifyCodeWeChatMessage.send("zhangsan", "9527");
}
}

输出类似:

1
2
3
通过邮件发送给 user@example.com,内容:欢迎注册系统
通过短信发送给 13800000000,内容:[加急] 您的订单支付异常,请及时处理
通过企业微信发送给 zhangsan,内容:您的验证码是:9527,5 分钟内有效。

这个结构下:

  • 新增消息类型,只需要新增 Message 子类;
  • 新增发送渠道,只需要新增 MessageSender 实现类;
  • 二者可以自由组合。

这就是桥接模式的价值。

chapter 12:新增一种消息类型

假设现在新增营销消息。

营销消息需要自动添加退订提示。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MarketingMessage extends Message {

public MarketingMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
String marketingContent = content + ",回复 TD 退订。";

messageSender.send(receiver, marketingContent);
}
}

它可以直接搭配已有发送渠道:

1
2
3
4
5
MessageSender smsSender = new SmsMessageSender();

Message marketingMessage = new MarketingMessage(smsSender);

marketingMessage.send("13800000000", "会员日优惠活动开始了");

不需要修改:

  • EmailMessageSender
  • SmsMessageSender
  • WeChatMessageSender

chapter 13:新增一种发送渠道

假设现在新增钉钉发送渠道。

1
2
3
4
5
6
7
public class DingTalkMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过钉钉发送给 " + receiver + ",内容:" + content);
}
}

它可以直接搭配已有消息类型:

1
2
3
4
5
MessageSender dingTalkSender = new DingTalkMessageSender();

Message urgentMessage = new UrgentMessage(dingTalkSender);

urgentMessage.send("team-group", "线上订单支付失败率过高");

不需要修改:

  • NormalMessage
  • UrgentMessage
  • VerifyCodeMessage
  • MarketingMessage

这就是“抽象部分”和“实现部分”独立扩展。

chapter 14:在 Spring Boot 中落地

在 Spring Boot 项目中,发送渠道通常会注册成 Bean。

先定义发送接口:

1
2
3
4
5
6
public interface MessageSender {

String channel();

void send(String receiver, String content);
}

邮件发送器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class EmailMessageSender implements MessageSender {

@Override
public String channel() {
return "EMAIL";
}

@Override
public void send(String receiver, String content) {
System.out.println("通过邮件发送给 " + receiver + ",内容:" + content);
}
}

短信发送器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class SmsMessageSender implements MessageSender {

@Override
public String channel() {
return "SMS";
}

@Override
public void send(String receiver, String content) {
System.out.println("通过短信发送给 " + receiver + ",内容:" + content);
}
}

企业微信发送器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class WeChatMessageSender implements MessageSender {

@Override
public String channel() {
return "WECHAT";
}

@Override
public void send(String receiver, String content) {
System.out.println("通过企业微信发送给 " + receiver + ",内容:" + content);
}
}

然后定义发送器路由器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class MessageSenderRouter {

private final Map<String, MessageSender> senderMap;

public MessageSenderRouter(List<MessageSender> senders) {
this.senderMap = senders.stream()
.collect(Collectors.toUnmodifiableMap(MessageSender::channel, sender -> sender));
}

public MessageSender getSender(String channel) {
MessageSender sender = senderMap.get(channel);

if (sender == null) {
throw new IllegalArgumentException("Unsupported message channel: " + channel);
}

return sender;
}
}

消息类型可以继续使用普通 Java 类:

1
2
3
4
5
6
7
8
9
10
public abstract class Message {

protected final MessageSender messageSender;

protected Message(MessageSender messageSender) {
this.messageSender = messageSender;
}

public abstract void send(String receiver, String content);
}

消息工厂根据消息类型创建不同消息对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import org.springframework.stereotype.Component;

@Component
public class MessageFactory {

private final MessageSenderRouter messageSenderRouter;

public MessageFactory(MessageSenderRouter messageSenderRouter) {
this.messageSenderRouter = messageSenderRouter;
}

public Message createMessage(String messageType, String channel) {
MessageSender sender = messageSenderRouter.getSender(channel);

if ("NORMAL".equals(messageType)) {
return new NormalMessage(sender);
}

if ("URGENT".equals(messageType)) {
return new UrgentMessage(sender);
}

if ("VERIFY_CODE".equals(messageType)) {
return new VerifyCodeMessage(sender);
}

throw new IllegalArgumentException("Unsupported message type: " + messageType);
}
}

业务服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

private final MessageFactory messageFactory;

public NotificationService(MessageFactory messageFactory) {
this.messageFactory = messageFactory;
}

public void send(String messageType, String channel, String receiver, String content) {
Message message = messageFactory.createMessage(messageType, channel);

message.send(receiver, content);
}
}

调用方式:

1
2
3
notificationService.send("URGENT", "SMS", "13800000000", "订单支付异常,请及时处理");
notificationService.send("VERIFY_CODE", "EMAIL", "user@example.com", "9527");
notificationService.send("NORMAL", "WECHAT", "zhangsan", "欢迎使用系统");

这个 Spring Boot 版本结合了:

  • 桥接模式;
  • 工厂模式;
  • Spring Bean 自动注入;
  • 路由表;
  • 面向接口编程。

真实项目里经常不是单一模式,而是多个模式组合使用。

模式不是单打独斗的武林高手,更像一套组合拳。

chapter 15:继续优化:消息类型也交给 Spring 管理

上面的 MessageFactory 里还有 if else

如果消息类型很多,可以继续抽象。

定义消息处理器接口:

1
2
3
4
5
6
public interface MessageTemplate {

String type();

String format(String content);
}

普通消息模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class NormalMessageTemplate implements MessageTemplate {

@Override
public String type() {
return "NORMAL";
}

@Override
public String format(String content) {
return content;
}
}

加急消息模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class UrgentMessageTemplate implements MessageTemplate {

@Override
public String type() {
return "URGENT";
}

@Override
public String format(String content) {
return "[加急] " + content;
}
}

验证码消息模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.stereotype.Component;

@Component
public class VerifyCodeMessageTemplate implements MessageTemplate {

@Override
public String type() {
return "VERIFY_CODE";
}

@Override
public String format(String content) {
return "您的验证码是:" + content + ",5 分钟内有效。";
}
}

然后定义消息模板路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class MessageTemplateRouter {

private final Map<String, MessageTemplate> templateMap;

public MessageTemplateRouter(List<MessageTemplate> templates) {
this.templateMap = templates.stream()
.collect(Collectors.toUnmodifiableMap(MessageTemplate::type, template -> template));
}

public MessageTemplate getTemplate(String type) {
MessageTemplate template = templateMap.get(type);

if (template == null) {
throw new IllegalArgumentException("Unsupported message type: " + type);
}

return template;
}
}

业务服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.springframework.stereotype.Service;

@Service
public class NotificationService {

private final MessageTemplateRouter messageTemplateRouter;

private final MessageSenderRouter messageSenderRouter;

public NotificationService(MessageTemplateRouter messageTemplateRouter,
MessageSenderRouter messageSenderRouter) {
this.messageTemplateRouter = messageTemplateRouter;
this.messageSenderRouter = messageSenderRouter;
}

public void send(String messageType, String channel, String receiver, String content) {
MessageTemplate template = messageTemplateRouter.getTemplate(messageType);
MessageSender sender = messageSenderRouter.getSender(channel);

String formattedContent = template.format(content);

sender.send(receiver, formattedContent);
}
}

这个版本其实更像“桥接模式 + 策略模式”。

两个维度都被拆开了:

  • 消息类型维度:MessageTemplate
  • 发送渠道维度:MessageSender

业务服务负责把两个维度组合起来。

这种写法在 Spring 项目中非常实用。

chapter 16:桥接模式和适配器模式的区别

桥接模式和适配器模式都属于结构型模式,而且都使用组合,但它们的目的完全不同。

对比项 桥接模式 适配器模式
使用时机 设计初期主动拆分维度 已有接口不兼容时进行适配
目的 让抽象和实现独立变化 让不兼容接口能协同工作
关注点 多维度扩展 接口转换
是否改变接口 不一定 通常会转换接口
典型场景 消息类型 × 发送渠道 第三方 SDK 接入
设计性质 事前设计 事后补救

一句话区分:

桥接模式是提前拆分变化维度。

适配器模式是后来解决接口不兼容。

例如:

1
Message message = new UrgentMessage(new SmsMessageSender());

这是桥接模式。

1
PaymentClient client = new AliPayClientAdapter(new AliPaySdkClient());

这是适配器模式。

chapter 17:桥接模式和策略模式的区别

桥接模式和策略模式也很容易混。

它们都会用接口和组合。

但两者侧重点不同。

对比项 桥接模式 策略模式
关注点 拆分两个独立变化维度 替换一组算法或行为
结构 抽象层持有实现层 上下文持有策略接口
目的 避免类爆炸 让算法可替换
典型数量 通常有两个维度 通常一个行为维度
示例 消息类型 × 发送渠道 不同优惠计算策略

策略模式解决的是:

同一个行为有多种实现,运行时选择一种。

桥接模式解决的是:

一个对象存在多个变化维度,需要拆开组合。

在 Spring 项目中,桥接模式和策略模式经常混合出现。

比如前面的最终版本:

1
2
MessageTemplate template = messageTemplateRouter.getTemplate(messageType);
MessageSender sender = messageSenderRouter.getSender(channel);

这里 MessageTemplateMessageSender 都可以看作策略,但整个结构体现的是桥接思想:两个维度独立变化。

chapter 18:桥接模式和装饰器模式的区别

装饰器模式也是通过组合扩展功能,但和桥接模式不同。

对比项 桥接模式 装饰器模式
目的 拆分维度,避免类爆炸 动态增强对象功能
结构 抽象层组合实现层 装饰器和被装饰者实现同一接口
关注点 横向组合多个维度 纵向叠加多个功能
示例 消息类型 + 发送渠道 给发送器增加日志、限流、重试

桥接模式像把两个维度拆开,再横向组合。

装饰器模式像一层一层往对象外面套能力。

例如:

1
MessageSender sender = new SmsMessageSender();

桥接是:

1
Message message = new UrgentMessage(sender);

装饰器可能是:

1
2
3
4
5
MessageSender sender = new RetryMessageSenderDecorator(
new LogMessageSenderDecorator(
new SmsMessageSender()
)
);

一个是“维度组合”,一个是“功能叠加”。

chapter 19:桥接模式和继承的关系

桥接模式常常被理解为:

用组合替代继承。

但更准确地说,它是:

把继承体系中多个混在一起的变化维度拆开,让它们通过组合协作。

继承不是不能用。

问题是当继承层级同时表达多个维度时,就会开始膨胀。

例如:

1
2
3
4
5
Message
├── EmailNormalMessage
├── SmsNormalMessage
├── EmailUrgentMessage
└── SmsUrgentMessage

这里一个类名里同时包含两个维度:

  • Email / Sms:发送渠道;
  • Normal / Urgent:消息类型。

这就是一个明显信号:

这个继承体系可能需要桥接模式。

桥接之后:

1
2
3
4
5
6
7
Message
├── NormalMessage
└── UrgentMessage

MessageSender
├── EmailMessageSender
└── SmsMessageSender

组合使用:

1
new UrgentMessage(new SmsMessageSender());

类名不再承载多个维度,结构更清晰。

chapter 20:桥接模式的优点

1. 避免类爆炸

把多个变化维度拆开,避免产生大量组合类。

2. 符合开闭原则

新增抽象类型或实现类型时,尽量不修改已有代码。

3. 提高扩展性

抽象部分和实现部分可以独立扩展。

4. 降低耦合

抽象部分不依赖具体实现类,只依赖实现接口。

5. 更适合多维度业务建模

例如类型 × 渠道、业务 × 平台、格式 × 存储等组合场景。

chapter 21:桥接模式的缺点

1. 增加理解成本

桥接模式会引入更多抽象层。

对于简单场景,可能会显得复杂。

2. 需要正确识别变化维度

如果变化维度识别错了,桥接模式反而会让设计变绕。

3. 可能过度设计

如果系统中只有一个维度变化,或者组合关系很少,就没必要使用桥接模式。

4. 调试链路可能变长

抽象层通过实现层完成工作,调用关系比直接继承更绕一点。

但在复杂系统里,这个代价通常是值得的。

chapter 22:适用场景

桥接模式适合以下场景。

1. 存在两个或多个独立变化维度

例如:

  • 消息类型 × 发送渠道;
  • 报表类型 × 导出格式;
  • 文件类型 × 存储平台;
  • 支付业务 × 支付渠道。

2. 使用继承会导致类爆炸

如果类名开始出现多个维度组合,比如:

1
2
3
4
SmsUrgentMessage
EmailUrgentMessage
SmsNormalMessage
EmailNormalMessage

就应该警惕。

3. 希望抽象和实现可以独立扩展

例如新增一个消息类型,不希望改发送渠道。

新增一个发送渠道,也不希望改消息类型。

4. 不希望业务代码依赖具体实现

通过接口和组合解耦具体实现。

5. 平台差异和业务类型差异都比较明显

例如多云存储、多支付平台、多消息渠道。

chapter 23:不适合使用的场景

以下场景不建议使用桥接模式。

1. 只有一个变化维度

如果只有消息类型变化,没有发送渠道变化,普通继承或者策略模式就够了。

2. 类数量很少且不会增长

如果只有两个类,长期不会扩展,就没必要提前桥接。

3. 维度之间不是独立变化

如果两个维度强绑定,不能自由组合,桥接模式意义不大。

比如某种消息类型只能通过某个固定渠道发送,组合空间很小,强行桥接可能增加复杂度。

4. 团队理解成本过高

桥接模式比适配器、策略、工厂更抽象。

如果团队对多维度设计不熟,建议先用更直观的接口和组合方式逐步演进。

chapter 24:真实项目中的实践建议

1. 看到类名里有多个维度,要警惕

例如:

1
2
3
4
AliPayRefundService
WxPayRefundService
AliPayPayService
WxPayPayService

这里可能有两个维度:

  • 支付渠道:AliPay、WxPay;
  • 业务动作:Pay、Refund。

可以考虑拆成:

1
2
PaymentOperation
PaymentChannel

2. 不要一开始就过度桥接

如果业务还不稳定,先保持简单。

等变化维度真的清晰后,再抽象也不迟。

桥接模式适合解决已经看得见的维度复杂度,而不是想象中的未来复杂度。

3. 桥接模式经常和工厂模式组合

桥接模式解决结构问题。

工厂模式负责根据配置创建组合对象。

例如:

1
Message message = messageFactory.createMessage(messageType, channel);

4. Spring 项目可以用 Map 路由降低 if else

例如:

1
2
Map<String, MessageSender>
Map<String, MessageTemplate>

这样新增实现类时,只需要新增 Bean。

5. 注意抽象接口不要设计得太厚

MessageSender 最好只关心发送。

不要塞入太多业务方法。

例如不要让发送器同时负责:

  • 订单状态判断;
  • 用户权限校验;
  • 营销规则计算;
  • 账单生成;
  • 库存处理。

否则它就不再是桥接实现层,而是业务黑洞。

6. 桥接不是为了消灭所有继承

桥接模式不是反继承。

它只是避免用继承表达多个独立变化维度。

单个维度内部仍然可以有继承或接口层次。

chapter 25:完整案例代码汇总

发送渠道接口

1
2
3
4
public interface MessageSender {

void send(String receiver, String content);
}

具体发送渠道

1
2
3
4
5
6
7
public class EmailMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过邮件发送给 " + receiver + ",内容:" + content);
}
}
1
2
3
4
5
6
7
public class SmsMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过短信发送给 " + receiver + ",内容:" + content);
}
}
1
2
3
4
5
6
7
public class WeChatMessageSender implements MessageSender {

@Override
public void send(String receiver, String content) {
System.out.println("通过企业微信发送给 " + receiver + ",内容:" + content);
}
}

消息抽象类

1
2
3
4
5
6
7
8
9
10
public abstract class Message {

protected final MessageSender messageSender;

protected Message(MessageSender messageSender) {
this.messageSender = messageSender;
}

public abstract void send(String receiver, String content);
}

具体消息类型

1
2
3
4
5
6
7
8
9
10
11
public class NormalMessage extends Message {

public NormalMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
messageSender.send(receiver, content);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class UrgentMessage extends Message {

public UrgentMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
String urgentContent = "[加急] " + content;

messageSender.send(receiver, urgentContent);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class VerifyCodeMessage extends Message {

public VerifyCodeMessage(MessageSender messageSender) {
super(messageSender);
}

@Override
public void send(String receiver, String content) {
String codeContent = "您的验证码是:" + content + ",5 分钟内有效。";

messageSender.send(receiver, codeContent);
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {

public static void main(String[] args) {
MessageSender emailSender = new EmailMessageSender();
MessageSender smsSender = new SmsMessageSender();
MessageSender weChatSender = new WeChatMessageSender();

Message normalEmailMessage = new NormalMessage(emailSender);
normalEmailMessage.send("user@example.com", "欢迎注册系统");

Message urgentSmsMessage = new UrgentMessage(smsSender);
urgentSmsMessage.send("13800000000", "您的订单支付异常,请及时处理");

Message verifyCodeWeChatMessage = new VerifyCodeMessage(weChatSender);
verifyCodeWeChatMessage.send("zhangsan", "9527");
}
}

chapter 26:一句话总结

桥接模式的本质是:

将抽象部分和实现部分拆开,用组合关系建立桥梁,让两个变化维度可以独立扩展。

它最适合解决“多维度变化导致类爆炸”的问题。

在 Java 后端中,常见场景包括:

  • 消息类型 × 发送渠道;
  • 文件类型 × 存储平台;
  • 支付业务 × 支付渠道;
  • 报表类型 × 导出格式;
  • 日志类型 × 输出目标。

如果一个类名里开始同时出现两个业务维度,就要想想:是不是该搭一座桥了。

桥接模式不是为了让代码显得抽象,而是为了让变化不会互相拖累。

好的桥接设计,像一座真正的桥:两边都能独立发展,中间还能稳定通行。

参考资料

  • Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
  • Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
  • Joshua Bloch. Effective Java.
  • Spring Framework Documentation: Core Technologies - The IoC Container.
  • Refactoring Guru: Bridge Pattern.
  • SourceMaking: Bridge Design Pattern.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:桥接模式
https://allendericdalexander.github.io/2026/04/01/java/design/08bridge-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议