设计模式 · 桥接模式(Bridge Pattern)java

前言

业务代码里大量 if-else,往往不是因为逻辑复杂,而是因为变化维度被写死在了一起。

以支付场景为例,支付渠道可能有微信、支付宝、银联,支付模式又可能有密码、刷脸、指纹。如果把这些组合都写进一个方法里,短期看实现很快,长期看每新增一种渠道或模式,都要改动原有代码,重复逻辑越来越多,维护成本也越来越高。

桥接模式要解决的正是这类问题:当系统中存在两个或多个可以独立变化的维度时,将它们拆分出来,通过组合建立连接,避免类数量和判断分支随着组合关系膨胀。

本文将以“支付渠道 × 支付模式”为例,讲解桥接模式如何把复杂的组合逻辑拆成清晰、可扩展的面向对象结构。

本文代码链接:https://github.com/likerhood/CodeDesignWork/tree/main/codedesign6.0-0 和6.0-1


一、核心定义

桥接模式(Bridge Pattern) 是一种结构型设计模式,将抽象部分实现部分分离,使它们可以独立变化。

  • 核心机制:在抽象类 A 中持有实现接口 B 的引用,通过构造函数由外部传入 B 的具体实现。这个接口 B 就是设计的”桥(Bridge)”
  • 解决问题:两个(或多个)独立变化的维度需要自由组合,避免产生子类数量的笛卡尔积爆炸
  • 口诀:抽象依赖实现接口,实现通过构造注入

支付渠道(微信/支付宝)× 支付模式(密码/刷脸/指纹)= 如果用继承需要 2×3=6 个子类,
用桥接只需要 2+3=5 个类,新增任何一侧都只加 1 个类。


二、标准体系结构图

角色 说明
Abstraction(抽象类) 持有 Implementor 接口引用,定义高层操作,构造函数注入实现
RefinedAbstraction(扩展抽象类) 继承 Abstraction,扩展具体业务逻辑(如微信支付、支付宝支付)
Implementor(实现接口) 定义实现维度的接口(如支付模式的风控接口)
ConcreteImplementor(具体实现) 实现 Implementor,提供具体行为(如刷脸风控、指纹风控)

就是 Abstraction 中那个 Implementor 类型的成员变量,两个维度通过它连接,可以独立扩展。


三、场景推演

第三方支付聚合平台需要同时支持:

  • 支付渠道:微信支付、支付宝支付(未来还会有更多)
  • 支付模式:密码支付、刷脸支付、指纹支付(未来还会有更多)

如果用 if-else 硬编码:2 个渠道 × 3 个模式 = 6 种判断分支,全部挤在一个方法里。
如果用继承:WxPayCypher、WxPayFace、WxPayFingerprint、ZfbPayCypher …… 产生 6 个子类,每新增一个渠道或模式,子类数量再次翻倍。
如果用桥接:渠道和模式独立扩展,新增渠道只加 1 个渠道类,新增模式只加 1 个模式类,互不干扰。


四、实战案例

4.1 需求分析

维度 类型 说明
支付渠道 微信支付(WxPay) 划账 + 调用风控接口
支付渠道 支付宝支付(ZfbPay) 划账 + 调用风控接口
支付模式 密码支付(PayCypher) 风控:校验环境安全,返回 boolean
支付模式 刷脸支付(PayFaceMode) 风控:校验脸部识别,返回 boolean
支付模式 指纹支付(PayFingerprintMode) 风控:校验指纹信息,返回 boolean

核心流程:用户发起支付 → 选定渠道 → 对应风控模式安全校验 → 校验通过则划账成功,否则拦截。


4.2 架构图

4.2.1 面条代码架构图

image-20260514130442103

4.2.2 桥接模式架构图

image-20260514130502203


4.3 时序图

4.3.1 面条代码时序图


4.3.2 桥接模式时序图


4.5 代码分析

4.5.1 面条代码(if-else / 硬编码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// tutorials-10.0-1 PayController.java
public boolean doPay(String uId, String tradeId, BigDecimal amount,
int channelType, int modeType) {
// 渠道:微信
if (1 == channelType) {
logger.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
if (1 == modeType) {
logger.info("密码支付,风控校验环境安全");
} else if (2 == modeType) {
logger.info("人脸支付,风控校验脸部识别");
} else if (3 == modeType) {
logger.info("指纹支付,风控校验指纹信息");
}
}
// 渠道:支付宝
else if (2 == channelType) {
logger.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
if (1 == modeType) {
logger.info("密码支付,风控校验环境安全"); // ← 与微信完全重复!
} else if (2 == modeType) {
logger.info("人脸支付,风控校验脸部识别");
} else if (3 == modeType) {
logger.info("指纹支付,风控校验指纹信息");
}
}
return true;
}

问题清单:

  • 支付模式的逻辑在每个渠道下完全重复,渠道 × 模式 = 代码量笛卡尔积
  • 新增渠道(如银联)必须再写一套全部模式的逻辑
  • 新增模式(如声纹)必须修改每个渠道的代码块
  • channelTypemodeType 用魔法数字(1/2/3),可读性差,极易出错
  • 测试调用:pay.doPay(..., 1, 2) — 调用方需记住数字含义,接口极不友好

4.5.2 桥接模式代码

(1)实现接口(桥的右侧)— 支付模式

1
2
3
4
// tutorials-10.0-2 IPayMode.java
public interface IPayMode {
boolean security(String uId); // 风控校验,返回是否安全
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 密码支付风控
public class PayCypher implements IPayMode {
public boolean security(String uId) {
logger.info("密码支付,风控校验环境安全");
return true;
}
}

// 刷脸支付风控
public class PayFaceMode implements IPayMode {
public boolean security(String uId) {
logger.info("人脸支付,风控校验脸部识别");
return true;
}
}

// 指纹支付风控
public class PayFingerprintMode implements IPayMode {
public boolean security(String uId) {
logger.info("指纹支付,风控校验指纹信息");
return true;
}
}

每个支付模式只关心自己的风控逻辑,互相独立,可单独测试,新增模式不影响任何渠道代码。


(2)抽象类(桥的左侧)— 支付渠道

1
2
3
4
5
6
7
8
9
10
11
12
// tutorials-10.0-2 Pay.java(核心:持有桥接口的引用)
public abstract class Pay {

protected IPayMode payMode; // ← 这就是"桥"!

public Pay(IPayMode payMode) {
this.payMode = payMode; // ← 外部注入,运行时决定使用哪种模式
}

// 抽象方法:由各渠道子类实现具体划账逻辑
public abstract String transfer(String uId, String tradeId, BigDecimal amount);
}

Pay 不知道也不关心 payMode 的具体类型,只知道它有 security() 方法。
这就是”依赖接口而非实现”的体现,也是桥接的精髓。


(3)具体抽象(扩展抽象类)— 各支付渠道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 微信支付
public class WxPay extends Pay {

public WxPay(IPayMode payMode) {
super(payMode); // 把支付模式传递给父类(桥)
}

@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
logger.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);

boolean security = payMode.security(uId); // ← 通过桥调用模式的风控
logger.info("模拟微信渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);

if (!security) {
logger.info("模拟微信渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0001"; // 拦截
}
logger.info("模拟微信渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0000"; // 成功
}
}
1
2
3
4
5
6
7
8
9
10
11
// 支付宝支付(结构与 WxPay 完全对称,渠道日志不同)
public class ZfbPay extends Pay {
public ZfbPay(IPayMode payMode) { super(payMode); }

@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
logger.info("模拟支付宝渠道支付划账开始。...");
boolean security = payMode.security(uId); // ← 同样通过桥调用
// ... 同 WxPay 逻辑
}
}

两个渠道类都不包含任何模式判断逻辑,只调用 payMode.security(),风控细节由注入的实现类负责。


(4)客户端调用 — 自由组合

1
2
3
4
5
6
7
8
9
10
11
// tutorials-10.0-2 ApiTest.java
@Test
public void test_pay() {
// 微信 + 刷脸:构造函数直接组合,无需任何 if-else
Pay wxPay = new WxPay(new PayFaceMode());
wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));

// 支付宝 + 指纹:同样的方式,换一个实现即可
Pay zfbPay = new ZfbPay(new PayFingerprintMode());
zfbPay.transfer("jlu19dlxo111", "100000109894", new BigDecimal(100));
}

所有可能的组合方式:

渠道 \ 模式 密码(PayCypher) 刷脸(PayFaceMode) 指纹(PayFingerprintMode)
微信(WxPay) new WxPay(new PayCypher()) new WxPay(new PayFaceMode()) new WxPay(new PayFingerprintMode())
支付宝(ZfbPay) new ZfbPay(new PayCypher()) new ZfbPay(new PayFaceMode()) new ZfbPay(new PayFingerprintMode())

新增”银联支付”只需新建 UnionPay extends Pay,无需改动任何模式类;
新增”声纹支付”只需新建 PayVoiceMode implements IPayMode,无需改动任何渠道类。


总结

桥接模式的核心,是将抽象部分和实现部分分离,让它们可以独立变化。

在本文案例中,支付渠道是一组变化维度,支付模式也是一组变化维度。如果使用 if-else 或单纯继承,每一种渠道和模式的组合都可能产生新的判断分支或子类,最终形成笛卡尔积式的代码膨胀。

桥接模式通过在抽象类中持有实现接口,将“支付渠道”和“支付模式”连接起来。渠道类只负责渠道划账流程,模式类只负责风控校验逻辑,二者通过接口组合使用。这样新增渠道只需要新增渠道类,新增支付模式只需要新增模式类,原有代码无需频繁修改。

因此,桥接模式适合用在存在多个独立变化维度,并且这些维度需要自由组合的场景中。它的用处不只是减少 if-else,更重要的是让系统结构更清晰,职责更单一,扩展更稳定。

桥接模式解决了组合问题,但调用方仍需手动 new WxPay(new PayFaceMode())。可结合抽象工厂模式策略模式 + Map 配置,将渠道和模式的映射关系配置化(如从配置文件或枚举中读取),实现完全无 if-else 的支付路由。