桥接模式(Bridge):将抽象与实现分离,让它们各自独立演化

桥接模式,说白了就是「把抽象和实现拆开,各玩各的」。

我刚开始接触这个模式时,觉得它跟策略模式有点像。后来在项目中踩过坑才明白——桥接模式解决的是「多维度的变化」问题。你想想看,如果一个类既要应对不同的抽象层次,又要应对不同的实现方式,直接继承下去会变成什么样?

嗯,那就是传说中的「类爆炸」。

为什么需要桥接模式?

假设你要开发一个消息推送系统。消息可以分「普通消息」「紧急消息」「加急消息」。推送方式有「短信」「邮件」「App推送」。

如果不用桥接模式,你可能会这样设计:

class NormalSmsMessage { ... }
class NormalEmailMessage { ... }
class NormalAppMessage { ... }
class UrgentSmsMessage { ... }
class UrgentEmailMessage { ... }
// ... 继续写下去,你会疯掉

3种消息类型 × 3种推送方式 = 9个类。如果再加一种消息类型或推送方式,类数量会成倍增长。

核心问题:抽象(消息类型)和实现(推送方式)在同一个继承体系中耦合在一起,导致变化互相影响。

桥接模式的核心思想

桥接模式的做法很简单:

  • 把「抽象部分」定义为一个抽象类或接口
  • 把「实现部分」定义为另一个接口
  • 抽象类中持有实现接口的引用
  • 两者通过「组合」的方式连接起来

这样,抽象和实现就可以各自独立变化,互不干扰。

结构图

下面这张图展示了桥接模式的核心结构:

Abstraction(抽象类) - impl: Implementor + operation() RefinedAbstraction + operation() Implementor(实现接口) + operationImpl() ConcreteImplA + operationImpl() ConcreteImplB + operationImpl() 桥接(组合) 抽象部分和实现部分通过组合关系连接 两者可以各自独立扩展,互不影响 示例:消息推送系统 Abstraction = 消息类型(普通/紧急/加急) Implementor = 推送方式(短信/邮件/App推送) 两者通过桥接组合,新增消息类型或推送方式都不影响对方

代码示例:消息推送系统

先定义实现部分的接口:

// 实现接口:推送方式
public interface MessageSender {
    void send(String message);
}

然后是实现类的具体实现:

public class SmsSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("通过短信发送:" + message);
    }
}

public class EmailSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("通过邮件发送:" + message);
    }
}

public class AppPushSender implements MessageSender {
    @Override
    public void send(String message) {
        System.out.println("通过App推送发送:" + message);
    }
}

接下来是抽象部分:

// 抽象类:消息
public abstract class Message {
    protected MessageSender sender;

    public Message(MessageSender sender) {
        this.sender = sender;
    }

    public abstract void send(String content);
}

精化抽象类:

public class NormalMessage extends Message {
    public NormalMessage(MessageSender sender) {
        super(sender);
    }

    @Override
    public void send(String content) {
        sender.send("[普通]" + content);
    }
}

public class UrgentMessage extends Message {
    public UrgentMessage(MessageSender sender) {
        super(sender);
    }

    @Override
    public void send(String content) {
        sender.send("[紧急]" + content);
    }
}

public class CriticalMessage extends Message {
    public CriticalMessage(MessageSender sender) {
        super(sender);
    }

    @Override
    public void send(String content) {
        sender.send("[加急]" + content);
    }
}

客户端使用:

public class Client {
    public static void main(String[] args) {
        // 普通消息 + 短信
        Message msg1 = new NormalMessage(new SmsSender());
        msg1.send("系统维护通知");

        // 紧急消息 + 邮件
        Message msg2 = new UrgentMessage(new EmailSender());
        msg2.send("服务器宕机");

        // 加急消息 + App推送
        Message msg3 = new CriticalMessage(new AppPushSender());
        msg3.send("数据库连接失败");
    }
}

桥接模式 vs 策略模式

我经常被问到这个问题。两者确实很像,但关注点不同:

对比维度 桥接模式 策略模式
关注点 抽象与实现的分离 算法的替换
变化维度 两个维度独立变化 一个维度(算法)变化
结构 抽象类持有实现接口 上下文类持有策略接口
典型场景 跨平台UI、多数据库驱动 排序算法选择、支付方式选择
我的经验:如果你发现一个类有两个独立变化的维度,而且用继承会导致类数量爆炸,那桥接模式就是你的解药。我在做支付网关时,支付渠道(微信/支付宝/银联)和支付方式(扫码/跳转/小程序)就是两个独立维度,用桥接模式后代码清爽多了。

适用场景

  • 一个类存在两个独立变化的维度
  • 不希望抽象和实现之间有固定的绑定关系
  • 抽象和实现都需要可扩展
  • 对具体实现的修改不影响客户端代码

优缺点

优点:

  • 抽象和实现分离,各自独立扩展
  • 符合开闭原则,新增抽象或实现都不修改现有代码
  • 实现细节对抽象透明

缺点:

  • 增加了系统的复杂度
  • 需要正确识别出两个独立变化的维度
  • 如果维度不多,直接用继承反而更简单
避坑指南:我曾经在一个项目中过度使用桥接模式,把本来只有一个变化维度的功能硬拆成两个维度,结果代码变得又绕又难懂。记住,桥接模式是为「多维变化」准备的,不要为了用模式而用模式。

实际应用案例

桥接模式在现实框架中随处可见:

  • JDBC驱动:JDBC接口是抽象,不同数据库的驱动是实现
  • Java AWT:Component是抽象,Peer是实现(不同操作系统)
  • 日志框架:Logger是抽象,Appender是实现(文件/控制台/网络)
  • UI框架:窗口是抽象,渲染引擎是实现(Windows/Mac/Linux)

说白了,桥接模式就是「组合优于继承」的经典体现。它告诉我们:当变化来自多个方向时,不要试图用继承来搞定一切,组合才是更优雅的答案。

我个人习惯在设计初期先问自己一个问题:「这个类未来会在哪些维度上变化?」如果答案是两个以上,桥接模式就该出场了。


公众号:蓝海资料掘金营,微信deep3321