重学Java设计模式-结构型模式-桥接模式
内容摘自:https://bugstack.cn/md/develop/design-pattern/2020-06-04-重学 Java 设计模式《实战桥接模式》.html#重学-java-设计模式-实战桥接模式「多支付渠道-微信、支付宝-与多支付模式-刷脸、指纹-场景」
桥接模式介绍
桥接模式的主要作用就是通过将抽象部分与实现部分分离,把多种可匹配的使用进行组合。说白了核心实现也就是在A类中含有B类接口,通过构造函数传递B类的实现,这个B类就是设计的桥
。
那么这样的桥接模式,在我们平常的开发中有哪些场景
JDBC多种驱动程序的实现、同品牌类型的台式机和笔记本平板、业务实现中的多类接口同组过滤服务等。这些场景都比较适合使用桥接模式进行实现,因为在一些组合中如果有如果每一个类都实现不同的服务可能会出现笛卡尔积,而使用桥接模式就可以非常简单。
案例场景模拟
随着市场的竞争在支付服务行业出现了微信和支付宝还包括一些其他支付服务,但是对于商家来说并不希望改变用户习惯。就像如果我的地摊只能使用微信或者只能使用支付宝付款,那么就会让我顾客伤心,鸡蛋灌饼也卖不动了。
在这个时候就出现了第三方平台,把市面上综合占据市场90%以上的支付服务都集中到自己平台中,再把这样的平台提供给店铺、超市、地摊使用,同时支持人脸、扫描、密码多种方式。
我们这个案例就模拟一个这样的第三方平台来承接各个支付能力,同时使用自家的人脸让用户支付起来更加容易。那么这里就出现了多支付与多模式的融合使用,如果给每一个支付都实现一次不同的模式,即使是继承类也需要开发好多。而且随着后面接入了更多的支付服务或者支付方式,就会呈爆炸似的扩展。
所以你现在可以思考一下这样的场景该如何实现?
桥接模式重构代码
接下来使用桥接模式来进行代码优化,也算是一次很小的重构。
从上面的ifelse
方式实现来看,这是两种不同类型的相互组合。那么就可以把支付方式和支付模式进行分离通过抽象类依赖实现类的方式进行桥接,通过这样的拆分后支付与模式其实是可以单独使用的,当需要组合时候只需要把模式传递给支付即可。
桥接模式的关键是选择的桥接点拆分,是否可以找到这样类似的相互组合,如果没有就不必要非得使用桥接模式。
1. 工程结构
itstack-demo-design-7-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design.pay
│ ├── channel
│ │ ├── Pay.java
│ │ ├── WxPay.java
│ │ └── ZfbPay.java
│ └── mode
│ ├── IPayMode.java
│ ├── PayCypher.java
│ ├── PayFaceMode.java
│ └── PayFingerprintMode.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
桥接模式模型结构
- 左侧
Pay
是一个抽象类,往下是它的两个支付类型实现;微信支付、支付宝支付。 - 右侧
IPayMode
是一个接口,往下是它的两个支付模型;刷脸支付、指纹支付。 - 那么,
支付类型
×支付模型
= 就可以得到相应的组合。 - 注意,每种支付方式的不同,刷脸和指纹校验逻辑也有差异,可以使用适配器模式进行处理,这里不是本文重点不做介绍,可以看适配器模式章节。
2. 代码实现
2.1 支付类型桥接抽象类
public abstract class Pay {
protected Logger logger = LoggerFactory.getLogger(Pay.class);
protected IPayMode payMode;
public Pay(IPayMode payMode) {
this.payMode = payMode;
}
public abstract String transfer(String uId, String tradeId, BigDecimal amount);
}
- 在这个类中定义了支付方式的需要实现的划账接口:
transfer
,以及桥接接口;IPayMode
,并在构造函数中用户方自行选择支付方式。 - 如果没有接触过此类实现,可以重点关注
IPayMode payMode
,这部分是桥接的核心。
2.2 两个支付类型的实现
微信支付
public class WxPay extends Pay {
public WxPay(IPayMode payMode) {
super(payMode);
}
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";
}
}
支付宝支付
public class ZfbPay extends Pay {
public ZfbPay(IPayMode payMode) {
super(payMode);
}
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";
}
}
- 这里分别模拟了调用第三方的两个支付渠道;微信、支付宝,当然作为支付综合平台可能不只是接了这两个渠道,还会有其很跟多渠道。
- 另外可以看到在支付的时候分别都调用了风控的接口进行验证,也就是不同模式的支付(
刷脸
、指纹
),都需要过指定的风控,才能保证支付安全。
2.3 定义支付模式接口
public interface IPayMode {
boolean security(String uId);
}
- 任何一个支付模式;刷脸、指纹、密码,都会过不同程度的安全风控,这里定义一个安全校验接口。
2.4 三种支付模式风控(刷脸、指纹、密码)
刷脸
public class PayFaceMode implements IPayMode{
protected Logger logger = LoggerFactory.getLogger(PayCypher.class);
public boolean security(String uId) {
logger.info("人脸支付,风控校验脸部识别");
return true;
}
}
指纹
public class PayFingerprintMode implements IPayMode{
protected Logger logger = LoggerFactory.getLogger(PayCypher.class);
public boolean security(String uId) {
logger.info("指纹支付,风控校验指纹信息");
return true;
}
}
密码
public class PayCypher implements IPayMode{
protected Logger logger = LoggerFactory.getLogger(PayCypher.class);
public boolean security(String uId) {
logger.info("密码支付,风控校验环境安全");
return true;
}
}
- 在这里实现了三种支付模式(刷脸、指纹、密码)的风控校验,在用户选择不同支付类型的时候,则会进行相应的风控拦截以此保障支付安全。
3. 测试验证
3.1 编写测试类
@Test
public void test_pay() {
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));
}
- 与上面的ifelse实现方式相比,这里的调用方式变得整洁、干净、易使用;
new WxPay(new PayFaceMode())
、new ZfbPay(new PayFingerprintMode())
- 外部的使用接口的用户不需要关心具体的实现,只按需选择使用即可。
- 目前以上优化主要针对桥接模式的使用进行重构
if
逻辑部分,关于调用部分可以使用抽象工厂
或策略模式
配合map结构,将服务配置化。因为这里主要展示桥接模式
,所以就不在额外多加代码,避免喧宾夺主。
3.2 测试结果
模拟测试场景;微信支付、人脸方式。
23:14:40.911 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟微信渠道支付划账开始。uId:weixin_1092033111 tradeId:100000109893 amount:100
23:14:40.914 [main] INFO o.i.demo.design.pay.mode.PayCypher - 人脸支付,风控校验脸部识别
23:14:40.914 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟微信渠道支付风控校验。uId:weixin_1092033111 tradeId:100000109893 security:true
23:14:40.915 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟微信渠道支付划账成功。uId:weixin_1092033111 tradeId:100000109893 amount:100
模拟测试场景;支付宝支付、指纹方式。
23:14:40.915 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟支付宝渠道支付划账开始。uId:jlu19dlxo111 tradeId:100000109894 amount:100
23:14:40.915 [main] INFO o.i.demo.design.pay.mode.PayCypher - 指纹支付,风控校验指纹信息
23:14:40.915 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟支付宝渠道支付风控校验。uId:jlu19dlxo111 tradeId:100000109894 security:true
23:14:40.915 [main] INFO o.i.demo.design.pay.channel.Pay - 模拟支付宝渠道支付划账成功。uId:jlu19dlxo111 tradeId:100000109894 amount:100
Process finished with exit code 0
- 从测试结果看内容是一样的,但是整体的实现方式有了很大的变化。所以有时候不能只看结果,也要看看过程