桥接模式:抽象与实现分离

说实话,桥接模式是我在实际项目中用得最多的设计模式之一。为什么?因为它解决了一个很现实的问题——当你的系统需要同时应对「多种变化维度」时,继承体系会迅速膨胀到失控。

我记得有一次接手一个消息推送系统,刚开始只有短信和邮件两种方式,后来加了微信、钉钉、App推送……每种消息还要分普通、紧急、定时三种类型。如果用继承,你算算看:2×3=6个类,后来变成4×3=12个类。再后来业务方说「每种消息还要区分国内和海外」……嗯,那画面太美我不敢看。

什么是桥接模式?

桥接模式的核心思想就一句话:把抽象部分和实现部分拆开,让它们各自独立变化

你想想看,传统的继承方式是把「消息类型」和「发送渠道」绑死在一个类里。而桥接模式通过组合的方式,让这两条线各自发展,互不干扰。

核心定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

说白了,就是「用组合代替继承」。我个人的习惯是,只要发现类名里同时包含两个维度的信息(比如「紧急短信消息」「定时邮件消息」),就该考虑桥接模式了。

桥接模式的结构

桥接模式涉及四个角色:

  • 抽象化角色(Abstraction):定义抽象接口,持有实现化角色的引用
  • 扩展抽象化角色(RefinedAbstraction):对抽象化角色的扩展
  • 实现化角色(Implementor):定义实现类的接口
  • 具体实现化角色(ConcreteImplementor):实现具体的业务逻辑

我画了一张图,帮你理清它们之间的关系:

抽象化角色 Message 扩展抽象化 NormalMessage / UrgentMessage 持有引用 实现化角色 MessageSender 具体实现化 EmailSender / SmsSender 桥接模式核心结构 抽象维度:消息类型(普通、紧急、定时) 实现维度:发送渠道(短信、邮件、微信) 两者通过组合关系桥接,各自独立变化

实战:消息发送系统

好,理论说完了,咱们直接上代码。我带你一步步实现一个支持多种消息类型和多种发送渠道的消息系统。

第一步:定义实现化角色

// 发送渠道接口
public interface MessageSender {
    void send(String message, String recipient);
}

第二步:实现具体发送渠道

public class EmailSender implements MessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【邮件】发送给 " + recipient + ": " + message);
        // 实际项目中这里会调用邮件API
    }
}

public class SmsSender implements MessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【短信】发送给 " + recipient + ": " + message);
        // 实际项目中这里会调用短信网关
    }
}

public class WeChatSender implements MessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【微信】发送给 " + recipient + ": " + message);
        // 实际项目中这里会调用微信模板消息接口
    }
}

第三步:定义抽象化角色

public abstract class Message {
    protected MessageSender sender;
    
    public Message(MessageSender sender) {
        this.sender = sender;
    }
    
    public abstract void send(String content, String recipient);
}

个人经验:这里用构造方法注入 sender,而不是用 setter。为什么?因为消息一旦创建,发送渠道就不应该再变了。我在项目中见过有人用 setter 动态切换渠道,结果线上出了「短信发到邮件里」的诡异问题……

第四步:扩展抽象化角色

public class NormalMessage extends Message {
    public NormalMessage(MessageSender sender) {
        super(sender);
    }
    
    @Override
    public void send(String content, String recipient) {
        // 普通消息直接发送
        sender.send("[普通]" + content, recipient);
    }
}

public class UrgentMessage extends Message {
    public UrgentMessage(MessageSender sender) {
        super(sender);
    }
    
    @Override
    public void send(String content, String recipient) {
        // 紧急消息加前缀,并且可以记录日志
        String urgentContent = "[紧急]" + content;
        sender.send(urgentContent, recipient);
        System.out.println("已记录紧急消息日志:" + urgentContent);
    }
}

public class ScheduledMessage extends Message {
    private LocalDateTime scheduleTime;
    
    public ScheduledMessage(MessageSender sender, LocalDateTime scheduleTime) {
        super(sender);
        this.scheduleTime = scheduleTime;
    }
    
    @Override
    public void send(String content, String recipient) {
        // 定时消息需要检查时间
        if (LocalDateTime.now().isAfter(scheduleTime)) {
            sender.send("[定时]" + content, recipient);
        } else {
            System.out.println("消息未到发送时间,已存入队列");
            // 实际项目中这里会放入延迟队列
        }
    }
}

第五步:客户端使用

public class Client {
    public static void main(String[] args) {
        // 创建发送渠道
        MessageSender emailSender = new EmailSender();
        MessageSender smsSender = new SmsSender();
        MessageSender wechatSender = new WeChatSender();
        
        // 组合使用:普通邮件
        Message normalEmail = new NormalMessage(emailSender);
        normalEmail.send("您的订单已发货", "user@example.com");
        
        // 组合使用:紧急短信
        Message urgentSms = new UrgentMessage(smsSender);
        urgentSms.send("账户异常登录,请立即处理", "13800138000");
        
        // 组合使用:定时微信
        Message scheduledWechat = new ScheduledMessage(
            wechatSender, 
            LocalDateTime.of(2025, 6, 1, 10, 0)
        );
        scheduledWechat.send("六一儿童节快乐!", "user_wechat_id");
    }
}

桥接模式的优势

维度 传统继承方式 桥接模式
类数量 M × N(消息类型×发送渠道) M + N
扩展性 新增一个维度,类数量爆炸 新增一个维度,只需加一个类
耦合度 高,抽象和实现绑死 低,两者通过接口解耦
可维护性 修改一个渠道影响所有消息类型 修改渠道只影响该渠道本身

避坑指南:我曾经在一个项目中过度使用桥接模式,把本来只有两个维度的系统拆成了五个维度。结果代码变得极其抽象,新同事看了三天没看懂。记住:桥接模式适用于两个及以上独立变化的维度,如果只有一个维度在变化,用策略模式就够了。

什么时候用桥接模式?

我总结了几条判断标准,你写代码时可以对照着看看:

  • 你的类名里出现了两个「形容词」(比如「紧急短信」「定时邮件」)
  • 系统需要在多个维度上独立扩展
  • 你不想让抽象接口和具体实现绑死
  • 继承体系已经膨胀到让你头疼

嗯,其实还有一条更简单的判断方法:当你发现自己在复制粘贴代码,只是把「短信」改成「邮件」、「普通」改成「紧急」的时候,就该停下来想想——是不是该用桥接模式了?

核心要点回顾:

  • 桥接模式 = 抽象 + 实现,通过组合连接
  • 用组合代替继承,避免类爆炸
  • 两个维度独立变化,互不干扰
  • 适合多维度变化的场景

桥接模式说白了就是「分而治之」的思想在代码层面的体现。把变化的部分拆开,让它们各自发展,再用一个「桥」把它们连起来。这个思路不光适用于消息系统,你在做支付系统、通知系统、报表系统时都会遇到类似的问题。

下次当你面对一堆「XX短信」「XX邮件」「XX微信」的类时,不妨试试桥接模式。相信我,你会爱上这种清爽的感觉。


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