每个多渠道支付系统都以同样的方式开始:一个 PaymentService 加一个巨大的 switch 语句。支付宝?调 alipayClient.pay()。微信?调 wechatClient.pay()。银联?调 unionPayClient.pay()。银行转账?那完全是另一种流程。每个渠道有自己的请求格式、自己的错误处理、自己的幂等机制。
代码长成怪物。不是因为业务逻辑复杂——每次都是三步(创建订单、调渠道、处理结果)。复杂来自耦合:每次加渠道你改 PaymentService。每次渠道改 API 你改 PaymentService。每次加功能(日志、重试、回调)你改每个 if-else 分支。
桥接模式把系统拆成两个层级:抽象层级(你做什么)和实现层级(你怎么做)。抽象层级定义支付流程。实现层级定义渠道特定行为。它们独立演化。加渠道意味着在实现层级加一个类。加功能意味着修改抽象层级。任一变更不触碰另一个。
if-else 支付怪兽
这是大多数支付服务几年后的样子:
```java @Service public class PaymentService {
public PaymentResult pay(PaymentRequest request) { String channel = request.getChannel(); if ("alipay".equals(channel)) { AlipayRequest alipayReq = new AlipayRequest(); alipayReq.setOutTradeNo(request.getOrderId()); alipayReq.setTotalAmount(request.getAmount().toString()); AlipayResponse resp = alipayClient.tradePay(alipayReq); if (resp.isSuccess()) { return PaymentResult.success(resp.getTradeNo()); } else { return PaymentResult.fail(resp.getSubCode(), resp.getSubMsg()); } } else if ("wechat".equals(channel)) { WechatPayRequest wechatReq = new WechatPayRequest(); WechatPayResponse resp = wechatClient.pay(wechatReq); } else if ("unionpay".equals(channel)) { // 银联完全不同 } throw new UnsupportedChannelException(channel); }} ```
问题不是 if-else 本身——而是每个分支包含完全不同的代码。请求构建、API 调用、错误处理、幂等——全部因渠道而异,全部活在同一个方法里。
这是最糟糕的耦合:抽象(支付流程)和实现(渠道细节)融合在一起。桥接模式把它们分开。
桥接模式:两个层级的解法
桥接模式定义两个独立层级,通过一个引用连接:
``` Abstraction (PaymentProcess) ├── RefinedAbstraction (StandardPaymentProcess) ├── RefinedAbstraction (RetryablePaymentProcess)
Implementation (PaymentChannel) ├── ConcreteImpl (AlipayChannel) ├── ConcreteImpl (WechatChannel) ├── ConcreteImpl (UnionPayChannel) ```
```java // 实现层级:渠道特定行为 public interface PaymentChannel { ChannelRequest buildRequest(PaymentOrder order); ChannelResponse callChannel(ChannelRequest request); PaymentResult parseResponse(ChannelResponse response); boolean supports(String channelCode); }
// 抽象层级:支付流程 public abstract class PaymentProcess { protected PaymentChannel channel;
public PaymentProcess(PaymentChannel channel) { this.channel = channel; } public PaymentResult process(PaymentOrder order) { ChannelRequest request = channel.buildRequest(order); ChannelResponse response = channel.callChannel(request); PaymentResult result = channel.parseResponse(response); return result; }}
// 细化抽象:加重试 public class RetryablePaymentProcess extends PaymentProcess { private final int maxRetries;
@Override public PaymentResult process(PaymentOrder order) { for (int i = 0; i <= maxRetries; i++) { PaymentResult result = super.process(order); if (result.isSuccess() || !result.isRetryable()) { return result; } } return PaymentResult.fail("MAX_RETRIES_EXCEEDED"); }} ```
两个层级独立演化:加渠道不改 PaymentProcess,加重试不改 PaymentChannel,改支付宝 API 不改其他渠道。
JDBC:桥接模式的教科书案例
JDBC 是桥接模式最纯粹的形式:
```java Connection conn = DriverManager.getConnection(url); PreparedStatement ps = conn.prepareStatement("SELECT * FROM users"); ResultSet rs = ps.executeQuery();
// 实现:MySQL ConnectionImpl / PostgreSQL PgConnection / OracleConnection ```
java.sql.Connection 是抽象,定义数据库操作接口。MySQL/PostgreSQL/Oracle 的驱动是实现,处理各自特定协议。你换数据库就换驱动(实现层级)。你换 JDBC 功能就升级 JDK(抽象层级)。任一变更不碰另一个。
Spring AbstractRoutingDataSource:桥接 + 策略
Spring 的 AbstractRoutingDataSource 是桥接结合策略做实现选择:
```java public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, DataSource> resolvedDataSources; @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } protected DataSource determineTargetDataSource() { Object lookupKey = determineCurrentLookupKey(); DataSource ds = resolvedDataSources.get(lookupKey); return ds != null ? ds : resolvedDefaultDataSource; } protected abstract Object determineCurrentLookupKey();} ```
桥接引用不是静态的——基于 Context 动态选择。抽象控制流程,委托给 Context 要求的实现。
桥接什么时候比策略更合适
策略模式:实现每次调用都变,基于当前 Context 选策略,策略是可互换替代品。
java PaymentStrategy strategy = selector.select(order); strategy.pay(order);
桥接模式:实现是稳定的变异维度,抽象和实现都是独立演化的层级,桥接引用设置一次不随调用变化。
java PaymentProcess process = new RetryablePaymentProcess(new AlipayChannel()); process.process(order1); process.process(order2); // 同渠道,不同订单
支付系统通常是桥接不是策略,因为抽象有变体(标准、可重试、异步),实现有变体(支付宝、微信、银联),两个维度独立演化。
桥接模式的工程陷阱
陷阱1:实现接口膨胀
实现接口长到包含两个层级需要的所有功能,变成上帝接口。修复:拆成核心 + 可选能力(RetryableChannel, AsyncChannel),抽象使用前检查 instanceof。
陷阱2:桥接引用泄漏
外部代码直接访问实现,抽象对流程的控制就丢了。修复:不暴露实现引用,给抽象加委托方法。
陷阱3:实现选择硬编码
如果抽象总是创建同一个实现,桥接就没意义。这是伪装成桥接的策略。选择应该发生在抽象外面——通过注入、配置或工厂。
桥接不是策略的别名
桥接是两个层级的独立演化。策略是每次调用的实现选择。支付渠道、数据库驱动、日志后端、消息协议——这些都是桥接候选,因为抽象和实现都是真实层级,有自己的演化时间线。
如果你一直叫这些"策略"用 Map 查找来建它们,你用了错误的模式。Map 是扁平选择器。桥接需要两个层级。你的系统有它们——你只是从来没给它们分开的接口。
对了,我在做的那个小程序「爪爪代码冒险记」里,桥接模式用卡皮巴拉走两条吊桥过河来类比——抽象桥和实现桥各自延伸,中间对接。比纯看代码直观,搜搜看。