# 定义
桥接模式的主要作用是通过将抽象部分与实现部分分离,将多种可匹配的使用进行组合。其核心实现是在 A 类中含有 B 类接口,通过构造函数传递 B 类的实现,这个 B 类就是设计的桥。
优点:
1. 抽象和实现的分离。
2. 优秀的扩展能力。
3. 实现细节对客户透明。
缺点:桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
使用场景:
1. 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
2. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
3. 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
>注意事项:对于两个独立变化的维度,使用桥接模式再适合不过了。
# 实践
这里来看看多支付和多模式组合场景
## 违背设计模式实现
在支付服务行业中,有微信、支付宝及一些其他支付服务,但是对于商家来说,并不能只接受某一种支付方式。如果商家只支持使用微信或支付宝付款,那么就会让顾客为难,商品销量也会受到影响。这时就出现了第三方平台,它们把市面上的多种支付服务都集中到自己平台中,再把这些平台提供给店铺、超市等商家使用,同时支持人脸支付、指纹支付和密码支付等多种方式。
```
@Slf4j
public class PayController {
public boolean doPay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType) {
// 微信支付
if (1 == channelType) {
log.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
if (1 == modeType) {
log.info("密码支付,风控校验环境安全");
} else if (2 == modeType) {
log.info("人脸支付,风控校验脸部识别");
} else if (3 == modeType) {
log.info("指纹支付,风控校验指纹信息");
}
}
// 支付宝支付
else if (2 == channelType) {
log.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
if (1 == modeType) {
log.info("密码支付,风控校验环境安全");
} else if (2 == modeType) {
log.info("人脸支付,风控校验脸部识别");
} else if (3 == modeType) {
log.info("指纹支付,风控校验指纹信息");
}
}
return true;
}
}
```
在 PayController 类中,有一个支付服务功能,提供必要字段:用户 ID、交易ID、金额、渠道和模式,传递给doPay方法,以及控制支付类型。以上的if…else应该是最差的一种写法,因为即使写if…else,也应该以优化的方式写,尽可能减少使用次数。
```
public class ApiTest {
@Test
public void test_pay() {
PayController pay = new PayController();
System.out.println("\r\n模拟测试场景;微信支付、人脸方式。");
pay.doPay("weixin_1092033111", "100000109893", new BigDecimal(100), 1, 2);
System.out.println("\r\n模拟测试场景;支付宝支付、指纹方式。");
pay.doPay("jlu19dlxo111","100000109894",new BigDecimal(100), 2, 3);
}
}
```
```
模拟测试场景;微信支付、人脸方式。
15:34:11.342 [main] INFO fun.lixj.design.controller.PayController - 模拟微信渠道支付划账开始。uId:weixin_1092033111 tradeId:100000109893 amount:100
15:34:11.345 [main] INFO fun.lixj.design.controller.PayController - 人脸支付,风控校验脸部识别
模拟测试场景;支付宝支付、指纹方式。
15:34:11.345 [main] INFO fun.lixj.design.controller.PayController - 模拟支付宝渠道支付划账开始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
15:34:11.345 [main] INFO fun.lixj.design.controller.PayController - 指纹支付,风控校验指纹信息
```
从测试结果看,已经满足了不同支付类型和支付模式的组合,但是这样的代码在后面的维护以及扩展过程中都会变得非常复杂。
## 使用桥接设计模式实现
从上面的 if…else 实现方式来看,这是两种不同类型的相互组合。可以把支付类型和支付模式分离,通过抽象类依赖实现类的方式进行桥接。按照这种方式拆分后,支付方式与支付模式可以单独使用,当需要组合时,只需要把模式传递给各类支付方式。
**桥接模式的关键是选择桥接点拆分,看能否找到这样类似的相互组合,如果没有就不用必须使用桥接模式。**
支付类型桥接抽象类
```
@Data
public abstract class Pay {
protected IPayMode payMode;
public Pay(IPayMode payMode) {
this.payMode = payMode;
}
public abstract String transfer(String uId, String tradeId, BigDecimal amount);
}
```
在这个类中定义了支付类型需要实现的划账接口transfer和桥接接口IPayMode,并在构造函数中实现用户方自行选择支付方式。如果没有接触过此类支付需求,可以重点关注IPayMode payMode,这部分是桥接模式的核心。
两种支付方式的实现,微信支付、支付宝支付
微信支付:
```
@Slf4j
public class WxPay extends Pay {
public WxPay(IPayMode payMode) {
super(payMode);
}
@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
log.info("模拟微信渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
boolean security = payMode.security(uId);
log.info("模拟微信渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
if (!security) {
log.info("模拟微信渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0001";
}
log.info("模拟微信渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0000";
}
}
```
支付宝支付:
```
@Slf4j
public class ZfbPay extends Pay {
public ZfbPay(IPayMode payMode) {
super(payMode);
}
@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
log.info("模拟支付宝渠道支付划账开始。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
boolean security = payMode.security(uId);
log.info("模拟支付宝渠道支付风控校验。uId:{} tradeId:{} security:{}", uId, tradeId, security);
if (!security) {
log.info("模拟支付宝渠道支付划账拦截。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0001";
}
log.info("模拟支付宝渠道支付划账成功。uId:{} tradeId:{} amount:{}", uId, tradeId, amount);
return "0000";
}
}
```
定义支付模式接口
```
public interface IPayMode {
boolean security(String uId);
}
```
三种支付模式风控(人脸、指纹和密码)
```
@Slf4j
public class PayCypher implements IPayMode {
@Override
public boolean security(String uId) {
log.info("密码支付,风控校验环境安全");
return true;
}
}
@Slf4j
public class PayFaceMode implements IPayMode{
@Override
public boolean security(String uId) {
log.info("人脸支付,风控校验脸部识别");
return true;
}
}
@Slf4j
public class PayFingerprintMode implements IPayMode {
@Override
public boolean security(String uId) {
log.info("指纹支付,风控校验指纹信息");
return true;
}
}
```
最后进行测试验证
```
public class ApiTest {
@Test
public void test_pay1() {
System.out.println("\r\n模拟测试场景;微信支付、人脸方式。");
Pay wxPay = new WxPay(new PayFaceMode());
wxPay.transfer("weixin_1092033111", "100000109893", new BigDecimal(100));
System.out.println("\r\n模拟测试场景;支付宝支付、指纹方式。");
Pay zfbPay = new ZfbPay(new PayFingerprintMode());
zfbPay.transfer("jlu19dlxo111","100000109894",new BigDecimal(100));
}
}
```
输出结果:
```
模拟测试场景;支付宝支付、指纹方式。
15:45:44.492 [main] INFO fun.lixj.design.channel.ZfbPay - 模拟支付宝渠道支付划账开始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
15:45:44.492 [main] INFO fun.lixj.design.mode.PayFingerprintMode - 指纹支付,风控校验指纹信息
15:45:44.492 [main] INFO fun.lixj.design.channel.ZfbPay - 模拟支付宝渠道支付风控校验。uId:jlu19dlxo111 tradeId:100000109894 security:true
15:45:44.492 [main] INFO fun.lixj.design.channel.ZfbPay - 模拟支付宝渠道支付划账成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100
```
与上面的 if…else 实现方式相比,这里的调用方式变得整洁、干净和易用:newWxPay(new PayFaceMode())、new ZfbPay(new PayFingerprintMode())。
外部使用接口的用户不需要关心具体的实现,只要按需选择使用即可。以上优化主要针对桥接模式的使用进行重构 if 逻辑部分。对于调用部分,可以使用抽象工厂或策略模式配合 MAP 结构。在 Map 结构中,Key 作为逻辑名称、Value 作为逻辑实现。通过这种方式把服务配置到 Map 键值对中,可以更方便地获取和使用,从而避免大量使用 if…else。
# 总结
从桥接模式的实现形式来看,它满足了单一职责和开闭原则,让每一部分内容都很清晰,易于维护和扩展。但如果实现的是高内聚的代码,则会很复杂。所以在选择重构代码时,需要考虑整体的设计。如果运用的设计模式不合理,也会让代码变得难以开发和维护。
任何一种设计模式的选择和使用都应该以符合场景为主,不能刻意使用。由于业务的复杂性,可能需要用到多种设计模式,才能将代码设计得更加合理。但这种经验需要从实际的项目中总结经验,并且不断地实践运用。

【设计模式】桥接模式