设计模式学习--桥接模式
桥接模式介绍
桥接模式的主要作用:通过抽象部分与实现部分相分离,把多种可匹配的使用相组合。
说白了就是在A类中含有B类的接口,通过构造函数传递B类的实现,这个B类就是设计的桥。
使用场景
那么我们平时开发中有哪些场景是使用到了该模式呢? 最典型的是JDBC多驱动程序的实现,还有同品牌类型的台式机和笔记本,平板。 业务实现中的多类接口同组实现等。因为在一些组合中如果每个类有多个不同的实现, 就会出现迪卡尔积,这样使用桥接模式就非常简单。
案例场景模拟
第三方平台如何承接微信的密码、指纹、刷脸支付,支付宝密码、指纹、刷脸支付的。实际场景肯定比这更多。
所以现在可以思考下这样的场景该如何实现?
用一坨坨代码实现
只用一个类直接实现,这是大多数初学者的想法
代码实现
需要导入的slf4j包mvn坐标
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.14.1</version> <scope>test</scope> </dependency>
package com.lrkj.bridgeMode.orignal; import com.lrkj.bridgeMode.enums.ChannelTypeEnum; import com.lrkj.bridgeMode.enums.PayModeEnum; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; @Slf4j public class PayController { /** * 模拟微信,支付宝支持的不同支付方式 * 以下方法是if-else的最差写法,即使写if-else也是可以优化的方式去写 * * @param uId 用户Id * @param tradeId 交易Id * @param amount 交易金额 * @param channelType 支付渠道(1:微信,2:支付宝) * @param modeType 支付方式(1:密码,2:指纹,3:人脸) * @return 是否支付成功 */ public Boolean doPayWorstWay(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("密码支付,风险安全校验环境安全"); log.info("密码支付成功"); } else if (2 == modeType) { log.info("指纹支付,风险校验指纹信息"); log.info("指纹支付成功"); } else if (3 == modeType) { log.info("人脸支付,风险校验脸部识别"); log.info("人脸支付成功"); } } // 支付宝支付 else if (2 == channelType) { log.info("模拟支付宝渠道划帐开始: uId:{} tradeId:{} amount:{}", uId, tradeId, amount); // 支付方式 if (1 == modeType) { log.info("密码支付,风险安全校验环境安全"); log.info("密码支付成功"); } else if (2 == modeType) { log.info("指纹支付,风险校验指纹信息"); log.info("指纹支付成功"); } else if (3 == modeType) { log.info("人脸支付,风险校验脸部识别"); log.info("人脸支付成功"); } } return true; } }
以上的if-else应该是最差的一种写法,即使写if-else也是可以优化去写的。
以下是自己写的几个优化方法(只把if-else部分写出来,是可以放在上面这个类里面调用的),可能还有更好方式
一般优化方式
/** * 模拟微信,支付宝支持的不同支付方式 * 以下方法是if-else的一般的写法,把支付渠道、支付方式用枚举来写 * 这样如果再添加其他支付渠道或者支付方式,直接修改枚举类即可 * * @param uId 用户Id * @param tradeId 交易Id * @param amount 交易金额 * @param channelType 支付渠道(1:微信,2:支付宝) * @param modeType 支付方式(1:密码,2:指纹,3:人脸) * @return 是否支付成功 */ public Boolean doPayNormalWay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType) { // 微信支付 if (ChannelTypeEnum.WEIXIN_CHANNEL.getCode() == channelType) { log.info("模拟微信渠道支持划账开始:uId:{} tradeId:{} amount:{}", uId, tradeId, amount); // 支付方式 if (PayModeEnum.CIPHERPAY.getCode() == modeType) { log.info("密码支付,风险安全校验环境安全"); log.info("密码支付成功"); } else if (PayModeEnum.FINGERPRINTPAY.getCode() == modeType) { log.info("指纹支付,风险校验指纹信息"); log.info("指纹支付成功"); } else if (PayModeEnum.FACEPAYMENT.getCode() == modeType) { log.info("人脸支付,风险校验脸部识别"); log.info("人脸支付成功"); } } // 支付宝支付 else if (ChannelTypeEnum.ZHIFUBAO_CHANNEL.getCode() == channelType) { log.info("模拟支付宝渠道划帐开始: uId:{} tradeId:{} amount:{}", uId, tradeId, amount); // 支付方式 if (PayModeEnum.CIPHERPAY.getCode() == modeType) { log.info("密码支付,风险安全校验环境安全"); log.info("密码支付成功"); } else if (PayModeEnum.FINGERPRINTPAY.getCode() == modeType) { log.info("指纹支付,风险校验指纹信息"); log.info("指纹支付成功"); } else if (PayModeEnum.FACEPAYMENT.getCode() == modeType) { log.info("人脸支付,风险校验脸部识别"); log.info("人脸支付成功"); } } return true; }
较好优化方式
/** * 模拟微信,支付宝支持的不同支付方式 * 以下方法是if-else的较好的写法,把支付渠道、支付方式用枚举来写 * 这样如果再添加其他支付渠道或者支付方式,直接修改枚举类即可 * * 同时抽区公共代码部分:支付方式 * 这样可以做到再添加支付方式时就需要修改一个方法 * * @param uId 用户Id * @param tradeId 交易Id * @param amount 交易金额 * @param channelType 支付渠道(1:微信,2:支付宝) * @param modeType 支付方式(1:密码,2:指纹,3:人脸) * @return 是否支付成功 */ public Boolean doPayBetterWay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType) { // // 微信支付 // if (ChannelTypeEnum.WEIXIN_CHANNEL.getCode() == channelType) { // log.info("模拟微信渠道支持划账开始:uId:{} tradeId:{} amount:{}", uId, tradeId, amount); // selectPayWay(modeType); // } // // 支付宝支付 // else if (ChannelTypeEnum.ZHIFUBAO_CHANNEL.getCode() == channelType) { // log.info("模拟支付宝渠道划帐开始: uId:{} tradeId:{} amount:{}", uId, tradeId, amount); // selectPayWay(modeType); // } // 最简洁方式,通过三元运算符直接判断模式 String payMode = channelType == ChannelTypeEnum.WEIXIN_CHANNEL.getCode() ? "微信":"支付宝"; log.info("模拟:{}渠道支持划账开始:uId:{} tradeId:{} amount:{}",payMode, uId, tradeId, amount); selectPayWay(modeType); return true; } private void selectPayWay(int modeType) { // 支付方式 if (PayModeEnum.CIPHERPAY.getCode() == modeType) { log.info("密码支付,风险安全校验环境安全"); log.info("密码支付成功"); } else if (PayModeEnum.FINGERPRINTPAY.getCode() == modeType) { log.info("指纹支付,风险校验指纹信息"); log.info("指纹支付成功"); } else if (PayModeEnum.FACEPAYMENT.getCode() == modeType) { log.info("人脸支付,风险校验脸部识别"); log.info("人脸支付成功"); } }
枚举类
package com.lrkj.bridgeMode.enums; /** * 支付渠道枚举类 */ public enum ChannelTypeEnum { // 微信支付 WEIXIN_CHANNEL(1, "微信支付"), // 支付宝支付 ZHIFUBAO_CHANNEL(2, "支付宝支付"); private Integer code; private String msg; ChannelTypeEnum(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
package com.lrkj.bridgeMode.enums; /** * 支持方式的枚举类 */ public enum PayModeEnum { // 密码支付 CIPHERPAY(1, "密码支付"), // 指纹支付 FINGERPRINTPAY(2, "指纹支付"), // 人脸支付 FACEPAYMENT(3, "人脸支付"); private Integer code; private String msg; PayModeEnum(Integer code, String msg) { this.code = code; this.msg = msg; } public Integer getCode() { return code; } public String getMsg() { return msg; } }
测试验证
测试类
package com.lrkj.bridgeMode.test; import com.lrkj.bridgeMode.orignal.PayController; import org.junit.Test; import java.math.BigDecimal; /** * 支付方式测试 * * 从测试结果来看,已经能够满足我们的不同支付和支付模式的组合需求,但是这样的代码在后面的维护及扩展都会变得非常复杂。 */ public class PayTest { PayController payController = new PayController(); @Test public void test_Pay(){ System.out.println("\r\n 模拟测试场景:微信 + 刷脸支付方式"); // 调用最差实现方式 //payController.doPayWorstWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 1, 3); // 调用一般实现方式 //payController.doPayNormalWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 1, 3); // 调用最佳实现方式 payController.doPayBetterWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 1, 3); System.out.println("\r\n 模拟测试场景:支付宝 + 指纹支付方式"); // 调用最差实现方式 //payController.doPayWorstWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 1, 2); // 调用一般实现方式 //payController.doPayNormalWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 1, 2); // 调用最佳实现方式 payController.doPayBetterWay("weixin_165845985", "2021090911590245", new BigDecimal("1000"), 2, 2); } }
测试结果
模拟测试场景:微信 + 刷脸支付方式 16:33:13.377 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 模拟:微信渠道支持划账开始:uId:weixin_165845985 tradeId:2021090911590245 amount:1000 16:33:13.382 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 人脸支付,风险校验脸部识别 16:33:13.382 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 人脸支付成功
模拟测试场景:支付宝 + 指纹支付方式 16:33:13.383 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 模拟:支付宝渠道支持划账开始:uId:weixin_165845985 tradeId:2021090911590245 amount:1000 16:33:13.383 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 指纹支付,风险校验指纹信息 16:33:13.383 [main] INFO com.lrkj.bridgeMode.orignal.PayController - 指纹支付成功
Process finished with exit code 0
桥接模式重构代码
工程结构
|-- src | |-- main | | |-- java | | | |-- com | | | |-- lrkj | | | |-- bridgeMode | | | | |-- channel | | | | | |-- Pay.java | | | | | |-- WxPay.java | | | | | |-- ZfbPay.java | | | | |-- mode | | | | | |-- IPayMode.java | | | | | |-- impl | | | | | |-- PayCrpherMode.java | | | | | |-- PayFaceMode.java | | | | | |-- PayFigerprintMode.java | | | | |-- test | | | | |-- PayBridgeTest.java
桥接模式模型结构
左侧Pay是一个抽象类,下面是它的两个支付类型实现类:微信支付,支付宝支付。
右侧IPayMode是一个接口,下面是它的两个支付模式实现类:刷脸支付,指纹支付。
支付类型 x 支付模式=相应的组合
代码实现
⽀支付类型桥接抽象类
package com.lrkj.bridgeMode.channel; import com.lrkj.bridgeMode.mode.IPayMode; import java.math.BigDecimal; /** * 支付模式抽象类 * * 目前只有两种支付模式,作为支付综合平台还会有其他支付渠道 */ 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 ,这部分是桥接的核⼼心。
两个⽀支付类型的实现
微信支付
package com.lrkj.bridgeMode.channel; import com.lrkj.bridgeMode.mode.IPayMode; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; /** * 微信支付方式 */ @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: {} security: {}",uId, tradeId, security); return "微信支付失败"; } log.info("模拟微信渠道支付划账成功:uId: {} tradeId: {} amount: {}", uId, tradeId, amount); return "微信支付成功"; } }
支付宝支付
package com.lrkj.bridgeMode.channel; import com.lrkj.bridgeMode.mode.IPayMode; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; /** * 支付宝支付方式 */ @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: {} security: {}",uId, tradeId, security); return "支付宝支付失败"; } log.info("模拟支付宝渠道支付划账成功:uId: {} tradeId: {} amount: {}", uId, tradeId, amount); return "支付宝支付成功"; } }
这⾥里里分别模拟了了调⽤用第三⽅方的两个⽀支付渠道;微信、⽀支付宝,当然作为⽀支付综合平台可能不不只是接 了了这两个渠道,还会有其很跟多渠道。 另外可以看到在⽀支付的时候分别都调⽤用了了⻛风控的接⼝口进⾏行行验证,也就是不不同模式的⽀支付( 刷脸 、 指 纹 ),都需要过指定的⻛风控,才能保证⽀支付安全。
定义⽀支付模式接⼝口
package com.lrkj.bridgeMode.mode; /** * 支付模式接口 */ public interface IPayMode { /** * 任何支付模式,刷脸,指纹,密码都会进行不同程度的安全风控校验,这里定义一个安全校验接口 * @param uId 用户id * @return 校验是否成功 */ Boolean security(String uId); }
三种⽀支付模式⻛风控(刷脸、指纹、密码)
刷脸
package com.lrkj.bridgeMode.mode.impl; import com.lrkj.bridgeMode.mode.IPayMode; import lombok.extern.slf4j.Slf4j; import org.junit.Test; /** * 刷脸支付模式 */ @Slf4j public class PayFaceMode implements IPayMode { @Override public Boolean security(String uId) { log.info("人脸支付,风控校验脸部识别"); return true; } }
指纹
package com.lrkj.bridgeMode.mode.impl; import com.lrkj.bridgeMode.mode.IPayMode; import lombok.extern.slf4j.Slf4j; /** * 指纹支付模式 */ @Slf4j public class PayFigerprintMode implements IPayMode { @Override public Boolean security(String uId) { log.info("指纹支付,风控校验指纹信息"); return true; } }
密码
package com.lrkj.bridgeMode.mode.impl; import com.lrkj.bridgeMode.mode.IPayMode; import lombok.extern.slf4j.Slf4j; /** * 密码支付模式 */ @Slf4j public class PayCrpherMode implements IPayMode { @Override public Boolean security(String uId) { log.info("密码支付,风控校验环境安全"); return true; } }
测试验证
编写测试类
package com.lrkj.bridgeMode.test; import com.lrkj.bridgeMode.channel.Pay; import com.lrkj.bridgeMode.channel.WxPay; import com.lrkj.bridgeMode.channel.ZfbPay; import com.lrkj.bridgeMode.mode.impl.PayFaceMode; import com.lrkj.bridgeMode.mode.impl.PayFigerprintMode; import org.junit.Test; import java.math.BigDecimal; /** * 测试桥接模式 */ public class PayBridgeTest { @Test public void testPayBridge(){ System.out.println("\r\n模拟测试场景;微信支付、人脸支付"); Pay wxPay = new WxPay(new PayFaceMode()); wxPay.transfer("weixin_15435434", "2021090915523214", new BigDecimal("1000")); System.out.println("\r\n模拟测试场景;支付宝支付、指纹支付"); Pay zfbPay = new ZfbPay(new PayFigerprintMode()); zfbPay.transfer("luikaida_123", "2021090915523214", new BigDecimal("1000")); } }
与上⾯面的ifelse实现⽅方式相⽐比,这⾥里里的调⽤用⽅方式变得整洁、⼲干净、易易使⽤用; new WxPay(new PayFaceMode()) 、 new ZfbPay(new PayFingerprintMode()) 外部的使⽤用接⼝口的⽤用户不不需要关⼼心具体的实现,只按需选择使⽤用即可。 ⽬目前以上优化主要针对桥接模式的使⽤用进⾏行行重构 if 逻辑部分,关于调⽤用部分可以使⽤用 抽象⼯工⼚厂 或 策略略模式 配合map结构,将服务配置化。因为这⾥里里主要展示 桥接模式 ,所以就不不在额外多加代 码,避免喧宾夺主
测试结果
模拟测试场景;微信支付、人脸支付 17:19:42.680 [main] INFO com.lrkj.bridgeMode.channel.WxPay - 模拟微信渠道支付划账开始: uId: weixin_15435434 tradeId: 2021090915523214 amount: 1000 17:19:42.683 [main] INFO com.lrkj.bridgeMode.mode.impl.PayFaceMode - 人脸支付,风控校验脸部识别 17:19:42.683 [main] INFO com.lrkj.bridgeMode.channel.WxPay - 模拟微信渠道支付风控校验: uId: weixin_15435434 tradeId: 2021090915523214 security: true 17:19:42.683 [main] INFO com.lrkj.bridgeMode.channel.WxPay - 模拟微信渠道支付划账成功:uId: weixin_15435434 tradeId: 2021090915523214 amount: 1000
模拟测试场景;支付宝支付、指纹支付 17:19:42.683 [main] INFO com.lrkj.bridgeMode.channel.ZfbPay - 模拟支付宝渠道支付划账开始: uId: luikaida_123 tradeId: 2021090915523214 amount: 1000 17:19:42.683 [main] INFO com.lrkj.bridgeMode.mode.impl.PayFigerprintMode - 指纹支付,风控校验指纹信息 17:19:42.683 [main] INFO com.lrkj.bridgeMode.channel.ZfbPay - 模拟支付宝渠道支付风控校验: uId: luikaida_123 tradeId: 2021090915523214 security: true 17:19:42.683 [main] INFO com.lrkj.bridgeMode.channel.ZfbPay - 模拟支付宝渠道支付划账成功:uId: luikaida_123 tradeId: 2021090915523214 amount: 1000
Process finished with exit code 0
从测试结果看内容是⼀一样的,但是整体的实现⽅方式有了了很⼤大的变化。所以有时候不不能只看结果,也 要看看过程
总结
通过模拟微信与⽀支付宝两个⽀支付渠道在不不同的⽀支付模式下, 刷脸 、 指纹 、 密码 ,的组合从⽽而体现 了了桥接模式的在这类场景中的合理理运⽤用。简化了了代码的开发,给后续的需求迭代增加了了很好的扩展 性。 从桥接模式的实现形式来看满⾜足了了单⼀一职责和开闭原则,让每⼀一部分内容都很清晰易易于维护和拓拓 展,但如果我们是实现的⾼高内聚的代码,那么就会很复杂。所以在选择重构代码的时候,需要考虑 好整体的设计,否则选不不到合理理的设计模式,将会让代码变得难以开发。 任何⼀一种设计模式的选择和使⽤用都应该遵顼符合场景为主,不不要刻意使⽤用。⽽而且统⼀一场景因为业务 的复杂从⽽而可能需要使⽤用到多种设计模式的组合,才能将代码设计的更更加合理理。但这种经验需要从 实际的项⽬目中学习经验,并提不不断的运⽤用。