Appearance
桥接模式 (Bridge Pattern)
一、定义
一句话概括:将抽象部分与实现部分分离,使它们可以独立变化。
官方定义(GoF):Decouple an abstraction from its implementation so that the two can vary independently.
桥接模式的核心思想是:用组合代替继承,将原本耦合在一起的两个维度(抽象和实现)分离成两个独立的类层次结构,通过"桥接"关联起来。这样,任一维度的变化都不会影响另一个维度。
二、解决的问题
2.1 什么场景下需要桥接模式?
- 多维度变化:一个类有多个独立变化的维度,如"形状 + 颜色"、"品牌 + 品类"、"消息类型 + 发送渠道"。
- 避免类爆炸:用继承处理多维度变化会导致子类数量指数级增长。
- 运行时切换:需要在运行时动态切换实现,而不是编译时固定。
- 隐藏实现细节:客户端不关心具体实现,只和抽象层交互。
2.2 不用桥接模式会有什么问题?
假设我们要设计一个"消息发送系统",消息类型有:普通消息、紧急消息;发送渠道有:短信、邮件、App 推送。
继承方案:
Message
├── NormalMessage
│ ├── NormalSMSMessage
│ ├── NormalEmailMessage
│ └── NormalAppMessage
└── UrgentMessage
├── UrgentSMSMessage
├── UrgentEmailMessage
└── UrgentAppMessage问题1:类爆炸 —— 3 种消息类型 × 3 种发送渠道 = 9 个子类,每增加一个维度,子类数量呈指数增长
问题2:违反单一职责 —— 每个子类既要处理消息类型逻辑,又要处理发送渠道逻辑
问题3:扩展困难 —— 新增一个发送渠道,需要为每种消息类型都新增子类
问题4:无法运行时切换 —— 消息类型和发送渠道在编译时绑定,无法动态切换三、结构
3.1 文字描述
桥接模式包含以下角色:
- Abstraction(抽象化):定义抽象接口,持有
Implementor引用。 - RefinedAbstraction(扩展抽象化):扩展
Abstraction,添加更多业务逻辑。 - Implementor(实现化):定义实现接口,通常是底层操作。
- ConcreteImplementor(具体实现化):实现
Implementor接口。
3.2 ASCII 类图
┌──────────────────────┐ ┌──────────────────────┐
│ Abstraction │ │ <<interface>> │
├──────────────────────┤ │ Implementor │
│ - impl: Implementor │◄─────────├──────────────────────┤
│ + operation() │ 桥接 │ + operationImpl() │
│ impl.operationImpl()│ └──────────┬───────────┘
└──────────┬───────────┘ │
│ ┌────────────┴───────────┐
┌──────────┴───────────┐ │ │
│ RefinedAbstraction │ ┌─────┴──────┐ ┌──────┴──────┐
├──────────────────────┤ │ Concrete │ │ Concrete │
│ + refinedOperation() │ │ ImplementA │ │ ImplementB │
└──────────────────────┘ └────────────┘ └─────────────┘
抽象层次结构(变) 实现层次结构(变)
桥接 = Abstraction 持有 Implementor 引用四、代码实现
4.1 基础实现
场景:消息发送系统
java
// ============ Implementor:消息发送渠道接口 ============
interface MessageSender {
void send(String content, String recipient);
}
// ============ ConcreteImplementor:具体发送渠道 ============
class SmsSender implements MessageSender {
@Override
public void send(String content, String recipient) {
System.out.println("[短信] 发送到 " + recipient + ",内容:" + content);
}
}
class EmailSender implements MessageSender {
@Override
public void send(String content, String recipient) {
System.out.println("[邮件] 发送到 " + recipient + ",内容:" + content);
}
}
class AppPushSender implements MessageSender {
@Override
public void send(String content, String recipient) {
System.out.println("[App推送] 发送到 " + recipient + ",内容:" + content);
}
}
// ============ Abstraction:抽象消息类 ============
abstract class Message {
protected MessageSender sender; // 桥接 —— 持有实现引用
protected Message(MessageSender sender) {
this.sender = sender;
}
public abstract void send(String content, String recipient);
}
// ============ RefinedAbstraction:扩展抽象消息 ============
class NormalMessage extends Message {
public NormalMessage(MessageSender sender) {
super(sender);
}
@Override
public void send(String content, String recipient) {
sender.send("[普通]" + content, recipient);
}
}
class UrgentMessage extends Message {
public UrgentMessage(MessageSender sender) {
super(sender);
}
@Override
public void send(String content, String recipient) {
sender.send("[紧急!!!]" + content, recipient);
}
}
// ============ 客户端测试 ============
public class BridgeDemo {
public static void main(String[] args) {
// 普通消息 通过 短信 发送
Message normalSms = new NormalMessage(new SmsSender());
normalSms.send("您的验证码是 123456", "13800138000");
// 紧急消息 通过 邮件 发送
Message urgentEmail = new UrgentMessage(new EmailSender());
urgentEmail.send("服务器 CPU 超过 90%", "admin@example.com");
// 紧急消息 通过 App 推送 发送
Message urgentPush = new UrgentMessage(new AppPushSender());
urgentPush.send("您有一笔大额交易待确认", "user_1001");
}
}4.2 进阶实现
4.2.1 多维度变化:品牌 + 品类
java
// ============ 维度一:品类(Abstract)============
abstract class Product {
protected Brand brand;
public Product(Brand brand) {
this.brand = brand;
}
public abstract void display();
}
// ============ 维度二:品牌(Implementor)============
interface Brand {
String getName();
double getPriceRate();
}
class Apple implements Brand {
@Override public String getName() { return "Apple"; }
@Override public double getPriceRate() { return 1.5; }
}
class Huawei implements Brand {
@Override public String getName() { return "华为"; }
@Override public double getPriceRate() { return 1.2; }
}
// ============ 具体品类(RefinedAbstraction)============
class Phone extends Product {
public Phone(Brand brand) { super(brand); }
@Override
public void display() {
double basePrice = 5000;
double finalPrice = basePrice * brand.getPriceRate();
System.out.println(brand.getName() + " 手机,价格: ¥" + finalPrice);
}
}
class Laptop extends Product {
public Laptop(Brand brand) { super(brand); }
@Override
public void display() {
double basePrice = 10000;
double finalPrice = basePrice * brand.getPriceRate();
System.out.println(brand.getName() + " 笔记本,价格: ¥" + finalPrice);
}
}
// 测试
// new Phone(new Apple()).display(); // Apple 手机,价格: ¥7500.0
// new Laptop(new Huawei()).display(); // 华为 笔记本,价格: ¥12000.04.2.2 JDBC 驱动 —— 最经典的桥接模式
JDBC 是桥接模式的典范:DriverManager 是抽象层,各数据库厂商的 Driver 是实现层。
java
// Abstraction: DriverManager
// Implementor: java.sql.Driver (接口)
// ConcreteImplementor: com.mysql.cj.jdbc.Driver, oracle.jdbc.OracleDriver 等
// 使用示例:
Class.forName("com.mysql.cj.jdbc.Driver"); // 加载具体实现
Connection conn = DriverManager.getConnection(url, user, password);
// DriverManager 不关心具体是哪个数据库,通过 Driver 接口桥接4.3 生产级实现
Spring Boot 多数据源动态路由
java
// ============ Implementor:数据源类型 ============
enum DataSourceType {
MASTER, SLAVE, ANALYTICS
}
// ============ 数据源持有者 ============
class DataSourceContextHolder {
private static final ThreadLocal<DataSourceType> CONTEXT = new ThreadLocal<>();
public static void setDataSource(DataSourceType type) {
CONTEXT.set(type);
}
public static DataSourceType getDataSource() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
// ============ Abstraction:动态数据源路由 ============
@Component
@Primary
class DynamicDataSource extends AbstractRoutingDataSource {
@Autowired
private DataSource masterDataSource;
@Autowired
private DataSource slaveDataSource;
@Autowired
private DataSource analyticsDataSource;
@PostConstruct
public void init() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource);
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
targetDataSources.put(DataSourceType.ANALYTICS, analyticsDataSource);
super.setTargetDataSources(targetDataSources);
super.setDefaultTargetDataSource(masterDataSource);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
// ============ 自定义注解实现读写分离 ============
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface ReadOnly {
DataSourceType value() default DataSourceType.SLAVE;
}
@Aspect
@Component
class DataSourceAspect {
@Around("@annotation(readOnly)")
public Object around(ProceedingJoinPoint point, ReadOnly readOnly) throws Throwable {
DataSourceContextHolder.setDataSource(readOnly.value());
try {
return point.proceed();
} finally {
DataSourceContextHolder.clear();
}
}
}
// ============ 使用示例 ============
@Service
class UserService {
@Autowired
private UserRepository userRepository;
// 写操作走主库
public void saveUser(User user) {
DataSourceContextHolder.setDataSource(DataSourceType.MASTER);
userRepository.save(user);
}
// 读操作走从库
@ReadOnly
public List<User> listUsers() {
return userRepository.findAll();
}
}五、优缺点
优点
| 优点 | 说明 |
|---|---|
| 分离抽象与实现 | 两个维度独立变化,互不影响 |
| 避免类爆炸 | 用组合代替多层继承,M × N 个类变为 M + N 个类 |
| 符合开闭原则 | 新增抽象或实现都无需修改现有代码 |
| 运行时切换 | 可以在运行时动态更换实现 |
| 隐藏细节 | 客户端只与抽象层交互,不关心底层实现 |
缺点
| 缺点 | 说明 |
|---|---|
| 设计复杂度高 | 需要正确识别两个独立变化的维度 |
| 增加代码量 | 需要额外的接口和抽象类 |
| 使用门槛 | 对于单一维度变化的场景,桥接模式是过度设计 |
六、适用场景
- 多维度独立变化:形状+颜色、品牌+品类、消息类型+发送渠道。
- 跨平台开发:GUI 框架(Windows/Mac/Linux 按钮实现)。
- 数据库驱动:JDBC 的 Driver 接口与各数据库厂商实现。
- 多数据源路由:读写分离、分库分表的数据源切换。
- 支付系统:支付方式(微信/支付宝)与支付场景(H5/App/PC)独立变化。
- 日志框架:日志级别(DEBUG/INFO/ERROR)与输出目标(控制台/文件/远程)分离。
七、JDK / Spring 框架中的实际应用
7.1 JDBC —— 最经典的桥接模式
java
// DriverManager (Abstraction) 桥接 Driver (Implementor)
// 不同数据库厂商实现 Driver 接口
Class.forName("com.mysql.cj.jdbc.Driver"); // 注册 MySQL 驱动
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test");
// 无论底层是什么数据库,使用方式完全一致7.2 SLF4J —— 日志门面
java
// SLF4J (Abstraction) 桥接 Logback/Log4j2 (Implementor)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(MyClass.class);
logger.info("Hello World");
// 无论底层是 Logback 还是 Log4j2,API 完全一致7.3 Spring 中的桥接
java
// 1. AbstractRoutingDataSource —— 多数据源路由
// 2. TransactionManager 层次结构 —— 抽象事务管理器桥接具体实现
// PlatformTransactionManager (Abstraction)
// ├── DataSourceTransactionManager (JDBC)
// └── JtaTransactionManager (JTA)八、与其他模式的关系
| 模式 | 关系 |
|---|---|
| 适配器模式 | 桥接是预先设计(分离抽象与实现),适配器是事后补救(接口不兼容)。桥接的两端都可以独立变化。 |
| 策略模式 | 桥接的结构与策略模式几乎相同,但意图不同:桥接强调"分离抽象与实现",策略强调"封装算法使其可互换"。 |
| 抽象工厂模式 | 抽象工厂可以创建和配置桥接模式的组合。 |
| 组合模式 | 桥接可以与组合模式结合,构建更复杂的树形结构。 |
九、面试常见问题
Q1:桥接模式和策略模式有什么区别?
答:两者结构相似(都是组合+接口),但意图不同:
- 桥接模式关注"结构"——将抽象和实现分离,让它们独立变化。桥接一般在设计阶段就确定,目的是避免类爆炸。
- 策略模式关注"行为"——封装一系列算法,让它们可以互换。策略一般在运行时动态切换。
- 典型例子:JDBC 是桥接(DriverManager + Driver),
Collections.sort()的Comparator是策略。
Q2:桥接模式和适配器模式有什么区别?
答:
- 桥接模式是事前设计,在设计阶段就将抽象和实现分离,让两者独立变化。
- 适配器模式是事后补救,当两个已有接口不兼容时,通过适配器转换接口。
- 桥接的目的是"分离",适配器的目的是"兼容"。
Q3:为什么 JDBC 是桥接模式?
答:DriverManager 是抽象层,java.sql.Driver 是实现层接口。不同数据库厂商(MySQL、Oracle、PostgreSQL)实现 Driver 接口。应用程序通过 DriverManager.getConnection() 获取连接,不关心底层是什么数据库。抽象层(DriverManager)和实现层(各厂商 Driver)可以独立变化——新增数据库不改变抽象层,抽象层升级也不影响实现层。
Q4:如何正确识别桥接模式的"两个维度"?
答:关键问题是:"这个类是否会因为两个独立的原因而变化?" 例如:
- 消息系统:消息类型(普通/紧急)和发送渠道(短信/邮件)是两个独立维度。
- 支付系统:支付方式(微信/支付宝)和支付场景(H5/App/PC)是两个独立维度。
- 如果只有一个变化维度,桥接模式就是过度设计。
Q5:桥接模式中,"桥"具体指什么?
答:"桥"指的是 Abstraction 中对 Implementor 的引用(组合关系)。通过这个引用,抽象层将具体操作委托给实现层。这个引用就像一座桥,连接了抽象层次结构和实现层次结构,让两者可以独立演化。
