本文转载自微信公众号「程序新视界」,作者二师兄。转载本文请联系程序新视界公众号。
前言
之前做三方支付系统的时候经常用到策略模式,比如用户会选择不同的支付方式,不同的支付方式又有不同的实现方法或银行接口调用。
现在做物联网系统,基于MQTT协议(TCP层面的协议)来传输数据,根据不同的请求(不同的Topic)处理不同的业务逻辑,也同样用到策略模式。
顿时感觉策略模式非常好用,而且结合Spring的实例化和注入功能,更加方便了。
今天就聊聊基于Spring(Boot)下策略模式的使用。
未使用策略模式时的处理
以物联网为例大家可能不够熟悉,下面就以支付场景为例。比如在支付的过程中我们可能会选择微信支付、支付宝支付或银卡支付。同时,银行卡又分不同银行,这里统一为银行卡。
最简单直接的代码实现形式如下:
- public void pay(String payType){
- if("alipay".equals(payType)){
- System.out.println("支付宝");
- }else if("wechatPay".equals(payType)){
- System.out.println("微信支付");
- } else if("bank".equals(payType)){
- System.out.println("银行卡支付");
- }
- }
这样对照设计模式,通常不符合两个原则:单一职责原则和开闭原则。
我们会发现当前类(或方法)不处理了多个业务的功能,一旦任何一个支付方式的修改都可能会影响到其他的支付方式。同时,无法做到对扩展开放,对修改关闭。新增其他支付方式时同样要修改ifelse判断,影响到其他的业务逻辑。
而策略模式通常就是解决这种有很多ifelse处理逻辑,从而提高代码的可维护性、可扩展性和可读性。
策略模式的轮廓
在对上述代码进行改造之前,先来了解一下策略模式的基本组成。
策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。
策略模式通常由以下几部分组成:
用类图来表示(省略策略工厂类)如下图:
image
基于Spring的策略模式实现
目前在实践中通常都是基于Spring的特性来实现策略模式,这里就以此为例来进行讲解。
策略类定义
上面已经提到,策略类用于定义功能的接口,对于支付场景则可命名为PaymentService或PaymentStrategy。
- public interface PaymentService {
- /**
- * 支付
- */
- PayResult pay(Order order);
- }
同时提供该策略类的不同实现类:AlipayService、WeChatPayService、BankPayService。
- @Service("alipay")
- public class AlipayService implements PaymentService {
- @Override
- public PayResult pay(Order order) {
- System.out.println("Alipay");
- return null;
- }
- }
- @Service("wechatPay")
- public class WeChatPayService implements PaymentService {
- @Override
- public PayResult pay(Order order) {
- System.out.println("WeChatPay");
- return null;
- }
- }
- @Service("bank")
- public class BankPayService implements PaymentService {
- @Override
- public PayResult pay(Order order) {
- System.out.println("BankPay");
- return null;
- }
- }
具体实现的实例化,可以通过一个PaymentFactory来进行构建存储,也可以直接利用@Autowired形式注入到Context的List或Map当中。
PaymentFactory的实现如下:
- public class PaymentFactory {
- private static final Map<String, PaymentService> payStrategies = new HashMap<>();
- static {
- payStrategies.put("alipay", new AlipayService());
- payStrategies.put("wechatPay", new WeChatPayService());
- payStrategies.put("bank", new BankPayService());
- }
- public static PaymentService getPayment(String payType) {
- if (payType == null) {
- throw new IllegalArgumentException("pay type is empty.");
- }
- if (!payStrategies.containsKey(payType)) {
- throw new IllegalArgumentException("pay type not supported.");
- }
- return payStrategies.get(payType);
- }
- }
通过static静态代码块来初始化对应的策略实现类,然后提供一个getPayment方法,根据支付类型来获取对应的服务。当然,通过static初始化的代码块是单例的无状态的,如果需要有状态的类则getPayment方法,每次都需要new一个新的对象。
- public static PaymentService getPayment1(String payType) {
- if (payType == null) {
- throw new IllegalArgumentException("pay type is empty.");
- }
- if ("alipay".equals(payType)) {
- return new AlipayService();
- } else if ("wechatPay".equals(payType)) {
- return new WeChatPayService();
- } else if ("bank".equals(payType)) {
- return new BankPayService();
- }
- throw new IllegalArgumentException("pay type not supported.");
- }
Context上下文
Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
上面通过工厂的形式创建策略类的实现类,当然也可以直接通过@Autowired注入到Context上下文中。
- @Component
- public class PaymentStrategy {
- @Autowired
- private final Map<String, PaymentService> payStrategies = new HashMap<>();
- public PaymentService getPayment(String payType) {
- if (payType == null) {
- throw new IllegalArgumentException("pay type is empty.");
- }
- if (!payStrategies.containsKey(payType)) {
- throw new IllegalArgumentException("pay type not supported.");
- }
- return payStrategies.get(payType);
- }
- }
上面通过@Autowired注解,将通过@Service实例化的PaymentService实现类,注入到map当中,其中key为实例化类的名称,value为具体的实例化类。
上面的getPayment代码与PaymentFactory中一致。当然,还可以在PaymentStrategy中封装一个pay方法,这样,客户端直接注入PaymentStrategy类调用pay方法即可。
- public PayResult pay(String payType,Order order){
- PaymentService paymentService = this.getPayment(payType);
- return paymentService.pay(order);
- }
改进方案
通过上面的代码基本上已经实现了策略模式,此时当新增加一个支付通道时,已经不用修改PaymentStrategy相关的代码,只用新增一个实现PaymentService接口的类即可。
但在接口定义这里,还是有优化空间的。比如,这里判断是通过Bean的名称来判断的,但某些情况下判断可能比较复杂或可能会同时执行多个Service。此时,就可以对PaymentService接口进行改进,新增一个检验是否支持该功能的判断方法。
- public interface PaymentService {
- boolean isSupport(Order order);
- /**
- * 支付
- */
- PayResult pay(Order order);
- }
由实现类来具体实现isSupport方法,判断自己支持哪些功能。
同时,上下文类也可以进一步利用Java8提供的Steam特性进行处理:
- @Component
- public class PaymentStrategy {
- /**
- * 此处用@Autowired将所有实例注入为List。
- */
- @Autowired
- private List<PaymentService> paymentServices;
- public void pay(Order order) {
- PaymentService paymentService = paymentServices.stream()
- .filter((service) -> service.isSupport(order))
- .findFirst()
- .orElse(null);
- if (paymentService != null) {
- paymentService.pay(order);
- } else {
- throw new IllegalArgumentException("pay type not supported.");
- }
- }
- }
通过进一步改造,程序变得更加灵活了。
小结
通过上面的代码实现,可以看出接口类只负责业务策略的定义,策略的具体实现可以单独放在实现类中也可以利用Spring的特性进行管理,Context上下文类负责业务逻辑的编排。
通过策略模式(或变种)的应用,实现了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。
最后,对于设计模式,只有在实践中不断的使用采用更加印象深刻。同时,在实现的过程中我们也并不一定非要拘泥于设计模式本身,也可以结合所使用的框架进行变种处理。
TOP云 (west.cn)12月1日消息,刚刚我们报道了两字母 域名 DO.COM由国外经纪公...
腾讯云与西门子数字化工业软件近日正式签订战略合作协议。依托于性能强大、安全...
对安全和监管高要求的场景 金融、证券等行业对业务部署的合规性,以及某些客户对...
前言 Apache RocketMQ 作为广为人知的开源消息中间件,诞生于阿里巴巴,于 2016 ...
客户简介 ATA(全美在线)是中国智能化考试测评服务的创始者与领导者,最大的计...
这两年我看到很多关于这方面的文章和帖子,这的确是一个非常方便的东西。但是,...
TOP云 (west.cn)2月26日消息,区块链、加密货币到底有多火,身在 域名 圈的小...
经过一番热烈,甚至激烈的推荐,我从使用PyCharm转到使用VSCode。在配置环境时,...
哪些 云服务器 便宜?虽然说买东西,大家最看重价格。不过, 云服务器 这种产品...
个人网站要 虚拟主机 吗?因为 虚拟主机 价格比较便宜,成本低,是很适合个人站...