桥接模式:消息推送系统的抽象与实现分离

各位同学,今天我们来聊聊桥接模式。说实话,这个模式的名字起得挺形象的——就像一座桥,连接了两岸,让两岸可以各自发展、互不干扰。

我记得刚入行那会儿,接手过一个消息推送系统。需求很简单:支持短信、邮件、App推送三种方式。我当时想都没想,直接写了三个类:SmsPushEmailPushAppPush。每个类里都实现了推送逻辑。嗯,当时觉得挺完美的。

结果呢?产品经理第二天就来了:「我们要区分紧急消息和普通消息,紧急消息要立刻推送,普通消息可以延迟。」

我一看,好嘛,现在得搞出6个类:UrgentSmsPushNormalSmsPushUrgentEmailPush……这要是再加个「定时消息」、「静默消息」,类数量直接爆炸。这就是典型的「类爆炸」问题。

核心问题:当抽象(消息类型)和实现(推送方式)都需要独立变化时,用继承会导致子类数量呈乘积级增长。

桥接模式的核心思想

说白了,桥接模式就是一句话:把抽象和实现解耦,让它们各自独立变化

怎么解耦?用组合代替继承。抽象层持有实现层的引用,而不是继承实现层。这样抽象可以随便变,实现也可以随便变,两者通过「桥」连接。

我个人习惯把桥接模式理解为「两维度的排列组合」。你想想看,消息类型是一维,推送方式是一维。用继承就是硬编码所有组合,用桥接就是让两个维度各自维护自己的类层次,运行时自由组合。

业务场景分析

回到我们的消息推送系统。抽象层有两个维度:

  • 消息类型:紧急消息、普通消息
  • 推送方式:短信、App推送、邮件推送

如果用桥接模式,我们只需要维护 2 + 3 = 5 个类,而不是 2 × 3 = 6 个类。如果未来再加一种消息类型(比如「定时消息」),只需要加 1 个类,而不是 3 个类。这就是桥接模式的价值所在。

代码实现

先定义推送方式的接口,也就是「实现部分」:

// 推送方式接口
public interface IMessageSender {
    void send(String message, String recipient);
}

然后实现三种具体的推送方式:

public class SmsSender implements IMessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【短信】发送给 " + recipient + ":" + message);
    }
}

public class EmailSender implements IMessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【邮件】发送给 " + recipient + ":" + message);
    }
}

public class AppPushSender implements IMessageSender {
    @Override
    public void send(String message, String recipient) {
        System.out.println("【App推送】发送给 " + recipient + ":" + message);
    }
}

接下来定义消息的抽象层:

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

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

    public abstract void push(String message, String recipient);
}

然后实现具体的消息类型:

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

    @Override
    public void push(String message, String recipient) {
        // 紧急消息加个前缀
        String urgentMsg = "[紧急] " + message;
        sender.send(urgentMsg, recipient);
    }
}

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

    @Override
    public void push(String message, String recipient) {
        // 普通消息直接发送
        sender.send(message, recipient);
    }
}

客户端使用起来非常灵活:

public class Client {
    public static void main(String[] args) {
        // 紧急消息 + 短信
        Message urgentSms = new UrgentMessage(new SmsSender());
        urgentSms.push("系统故障,请立即处理!", "13800138000");

        // 普通消息 + 邮件
        Message normalEmail = new NormalMessage(new EmailSender());
        normalEmail.push("本周周报已生成", "manager@company.com");

        // 紧急消息 + App推送
        Message urgentApp = new UrgentMessage(new AppPushSender());
        urgentApp.push("服务器宕机!", "admin");
    }
}

个人经验:我在实际项目中,经常把桥接模式和工厂模式配合使用。客户端不需要自己组合,而是通过一个工厂方法根据配置返回合适的组合对象。这样客户端代码更干净。

桥接模式的结构图

下面我用一张SVG图来展示桥接模式的核心结构,方便你理解抽象和实现是如何分离的:

桥接模式结构图 Message(抽象) - sender: IMessageSender + push(msg, recipient) UrgentMessage + push(): 加[紧急]前缀 NormalMessage + push(): 直接发送 IMessageSender(接口) + send(msg, recipient) SmsSender + send(): 短信通道 EmailSender + send(): 邮件通道 AppPushSender 桥接(组合) 抽象层 实现层

避坑指南:我曾经在一个项目中,把桥接模式和策略模式搞混了。两者的区别在于:策略模式关注的是「算法可替换」,桥接模式关注的是「抽象和实现可独立变化」。简单说,策略模式是行为层面的,桥接模式是结构层面的。

什么时候用桥接模式?

我总结了三个典型的适用场景:

  1. 类爆炸预警:当你发现类的数量开始呈乘积级增长时,赶紧考虑桥接模式。
  2. 两个维度独立变化:抽象和实现都需要扩展,而且互不影响。比如消息类型和推送方式。
  3. 运行时切换实现:需要在运行时动态选择实现方式。桥接模式天然支持这一点。

桥接模式 vs 其他模式

模式 关注点 典型场景
桥接模式 抽象与实现解耦 消息推送、跨平台UI
策略模式 算法可替换 支付方式、排序算法
适配器模式 接口转换 旧系统对接、第三方SDK

总结一下:桥接模式的核心就四个字——「组合优先」。当你的系统出现多个变化维度时,别急着用继承去硬编码所有组合。停下来想想,能不能用桥接模式把维度拆开,让它们各自独立演化。

我个人觉得,桥接模式是设计模式中「性价比」很高的一个。它不像工厂模式那样随处可见,但一旦用对地方,代码的可维护性会提升一个档次。你想想看,如果当初我的消息推送系统用了桥接模式,后来加「定时消息」的时候,只需要加一个类,而不是三个类。这就是设计模式的力量。

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