Skip to content

桥接模式 (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.0

4.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 个类
符合开闭原则新增抽象或实现都无需修改现有代码
运行时切换可以在运行时动态更换实现
隐藏细节客户端只与抽象层交互,不关心底层实现

缺点

缺点说明
设计复杂度高需要正确识别两个独立变化的维度
增加代码量需要额外的接口和抽象类
使用门槛对于单一维度变化的场景,桥接模式是过度设计

六、适用场景

  1. 多维度独立变化:形状+颜色、品牌+品类、消息类型+发送渠道。
  2. 跨平台开发:GUI 框架(Windows/Mac/Linux 按钮实现)。
  3. 数据库驱动:JDBC 的 Driver 接口与各数据库厂商实现。
  4. 多数据源路由:读写分离、分库分表的数据源切换。
  5. 支付系统:支付方式(微信/支付宝)与支付场景(H5/App/PC)独立变化。
  6. 日志框架:日志级别(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引用(组合关系)。通过这个引用,抽象层将具体操作委托给实现层。这个引用就像一座桥,连接了抽象层次结构和实现层次结构,让两者可以独立演化。