跳转至

桥接模式

从消息通知维度爆炸说起

告警通知系统需要支持两个维度的组合:消息紧急程度(普通、紧急、严重)× 发送渠道(短信、邮件、微信)。用继承来实现所有组合,就需要 UrgencySmsSenderUrgencyEmailSenderSevereSmsSender……3×3=9 个类。再加一种渠道,就要多写 3 个类——类数量按**乘法**增长,稍有扩展就爆炸。

桥接模式的解法是:把两个维度**各自独立成体系**,再用**对象组合**桥接起来。新增渠道只需新增一个渠道类,不碰任何紧急程度类;新增紧急程度同理,扩展从"乘法"变成"加法"。

🔍 定义

桥接模式(Bridge)将抽象部分与其实现部分分离,使两者可以独立变化。通过**组合代替继承**,避免多维度扩展时的类爆炸。

⚠️ 不使用桥接存在的问题

假设消息通知系统有两个维度的变化:消息类型(紧急、普通)和**发送渠道**(邮件、短信)。

用继承实现所有组合:

BridgeBadExample.java
package com.example.structural.bridge;

/**
 * 桥接模式 - 反例
 * 问题:用继承实现"通知类型 × 发送渠道"的组合,导致类爆炸
 */
public class BridgeBadExample {
    public static void main(String[] args) {
        // ❌ 每种组合都是一个独立类
        new UrgentEmailNotification().send("服务器宕机!");
        new UrgentSmsNotification().send("服务器宕机!");
        new NormalEmailNotification().send("每日报告");
        // 新增一种渠道(如微信),要为所有通知类型各新增一个子类 ❌
    }
}

// ❌ 类爆炸:通知类型 × 渠道 = 每种组合一个类
abstract class NotificationBad {
    public abstract void send(String message);
}

class UrgentEmailNotification extends NotificationBad {
    @Override
    public void send(String message) {
        System.out.println("[紧急][Email] " + message);
    }
}

class UrgentSmsNotification extends NotificationBad {
    @Override
    public void send(String message) {
        System.out.println("[紧急][SMS] " + message);
    }
}

class NormalEmailNotification extends NotificationBad {
    @Override
    public void send(String message) {
        System.out.println("[普通][Email] " + message);
    }
}

class NormalSmsNotification extends NotificationBad {
    @Override
    public void send(String message) {
        System.out.println("[普通][SMS] " + message);
    }
}

两个维度各自增长,类数量按乘法膨胀——这正是桥接模式要解决的问题。

🏗️ 设计模式结构说明

%%{init: {'themeVariables': {'noteBkgColor': 'transparent', 'noteBorderColor': '#768390'}}}%%
classDiagram
    classDef default fill:transparent,stroke:#768390
    class Notification {
        <<abstract>>
        #channel: MessageChannel
        +Notification(channel)
        +send(message) void
    }
    class UrgentNotification {
        +send(message) void
    }
    class NormalNotification {
        +send(message) void
    }
    class MessageChannel {
        <<interface>>
        +deliver(message) void
    }
    class EmailChannel {
        +deliver(message) void
    }
    class SmsChannel {
        +deliver(message) void
    }
    Notification <|-- UrgentNotification
    Notification <|-- NormalNotification
    Notification o--> MessageChannel
    MessageChannel <|.. EmailChannel
    MessageChannel <|.. SmsChannel
    note for Notification "抽象(Abstraction)"
    note for UrgentNotification "扩展抽象(RefinedAbstraction)"
    note for MessageChannel "实现接口(Implementor)"
    note for EmailChannel "具体实现(ConcreteImplementor)"

抽象层(Notification 及子类)通过组合持有实现层(MessageChannel),两个维度各自独立扩展。

💻 设计模式举例说明

BridgeExample.java
package com.example.structural.bridge;

/**
 * 桥接模式 - 正例
 * 将"通知类型"与"发送渠道"解耦:两个维度独立扩展,互不影响
 */
public class BridgeExample {
    public static void main(String[] args) {
        // ✅ 自由组合:不同通知类型 × 不同渠道
        Notification urgent = new UrgentNotification(new EmailChannel());
        urgent.send("服务器宕机!");

        Notification urgentSms = new UrgentNotification(new SmsChannel());
        urgentSms.send("服务器宕机!");

        Notification normal = new NormalNotification(new EmailChannel());
        normal.send("每日报告");

        // ✅ 新增微信渠道:只需加一个 WechatChannel,不需要修改任何 Notification 类
    }
}

// 实现维度接口:发送渠道
interface MessageChannel {
    void sendMessage(String prefix, String message);
}

// 具体实现:邮件
class EmailChannel implements MessageChannel {
    @Override
    public void sendMessage(String prefix, String message) {
        System.out.println("[Email][" + prefix + "] " + message);
    }
}

// 具体实现:短信
class SmsChannel implements MessageChannel {
    @Override
    public void sendMessage(String prefix, String message) {
        System.out.println("[SMS][" + prefix + "] " + message);
    }
}

// 抽象维度:通知类型(持有渠道引用,这就是"桥")
abstract class Notification {
    protected final MessageChannel channel; // 桥接点

    public Notification(MessageChannel channel) { this.channel = channel; }

    public abstract void send(String message);
}

// 具体抽象:紧急通知
class UrgentNotification extends Notification {
    public UrgentNotification(MessageChannel channel) { super(channel); }

    @Override
    public void send(String message) {
        channel.sendMessage("紧急", "⚠ " + message); // 委托给渠道
    }
}

// 具体抽象:普通通知
class NormalNotification extends Notification {
    public NormalNotification(MessageChannel channel) { super(channel); }

    @Override
    public void send(String message) {
        channel.sendMessage("普通", message);
    }
}

⚖️ 优缺点

优点:

  • 消除多维度继承导致的类爆炸
  • 符合**开闭原则**:两个维度可以独立扩展
  • 符合**单一职责原则**:抽象和实现各自只负责自己的变化

缺点:

  • 需要预先识别出两个独立变化的维度,对系统设计要求较高
  • 增加代码复杂度,对简单场景可能过度设计

🔗 与其它模式的关系

相似模式防混淆:

模式 关注点 时机
桥接(Bridge) 分离两个变化维度的结构层次 设计阶段预先规划
适配器(Adapter) 兼容不兼容的接口 事后弥补已有接口不匹配
策略(Strategy) 替换算法/行为 关注行为,不涉及结构分离

组合使用:

桥接可与抽象工厂配合——由工厂负责创建特定组合的实现层对象(如生产适配当前平台的 MessageChannel)。

🗂️ 应用场景

  • 需要在两个独立维度上扩展的系统(如"类型 × 平台"、"形状 × 颜色")
  • 希望在运行时切换实现(如动态切换通知渠道)
  • JDK:JDBC 驱动设计——Connection/Statement 是抽象,不同数据库驱动是实现

🏭 工业视角

JDBC 是桥接模式最权威的工业案例

GoF 对桥接模式的定义——"将抽象和实现解耦,让它们可以独立变化"——读起来令人困惑,因为这里的"抽象"和"实现"**不是**抽象类与实现类的意思。JDBC 是最好的诠释:

JDBC 切换数据库只需换一行
1
2
3
4
5
6
7
8
9
// 切换到 MySQL:只改这一行(或改配置文件)
Class.forName("com.mysql.cj.jdbc.Driver"); // MySQL Connector/J 8.x 推荐(旧名 com.mysql.jdbc.Driver 在 8.x 中已弃用)
// 切换到 Oracle:
// Class.forName("oracle.jdbc.driver.OracleDriver");

String url = "jdbc:mysql://localhost:3306/mydb";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");

在这里,DriverManager / Connection / Statement 是"抽象"(与具体数据库无关的操作骨架),com.mysql.cj.jdbc.Driver 等是"实现"(真正与数据库通信的类库)。两者通过 DriverManager.registerDriver() 组合在一起,独立演化,互不侵入。

桥接模式的两种理解方式

第一种(GoF 原版):将抽象类库与实现类库解耦,通过**对象组合**桥接,JDBC 是典型。第二种(更通用):当一个类存在两个或多个**独立变化的维度**时,用组合代替继承,避免类数量指数级增长。两种理解的代码结构相同,后者更容易在日常设计中识别和应用。

消息通知系统:识别"两个独立维度"的实战示范

王争用告警通知系统演示了桥接模式的第二种理解方式。原始设计把"消息紧急程度"和"发送渠道"混在一个 Notification 类里,导致大量 if-else。重构思路是**识别两个独立维度,将其拆分为独立的继承体系,再通过组合桥接**:

桥接模式重构后:抽象层与实现层独立扩展
// 实现层:发送渠道(独立变化的维度 1)
public interface MsgSender {
    void send(String message);
}
public class TelephoneMsgSender implements MsgSender { /*...*/ }
public class EmailMsgSender    implements MsgSender { /*...*/ }
public class WechatMsgSender   implements MsgSender { /*...*/ }

// 抽象层:消息紧急程度(独立变化的维度 2)
public abstract class Notification {
    protected MsgSender msgSender; // ← 桥接点
    public Notification(MsgSender msgSender) {
        this.msgSender = msgSender;
    }
    public abstract void notify(String message);
}
public class SevereNotification extends Notification {
    public void notify(String message) { msgSender.send("[严重] " + message); }
}
public class UrgencyNotification extends Notification { /*...*/ }

新增一种渠道只需新增 MsgSender 实现;新增一种紧急级别只需新增 Notification 子类。两个维度**互不影响**,符合开闭原则。

桥接模式在工程中并不常见

桥接模式要求**设计阶段**就能预判出两个独立变化的维度,门槛较高。实际项目中如果维度只有一个,或两个维度耦合紧密,强行套用反而增加复杂度。相比之下,第二种理解方式("组合代替继承")在日常重构中更频繁出现,你可以把它视为"组合优于继承"原则的一种具体落地结构。