再一次实战策略模式,真是太好用了

开发 前端
之前做三方支付系统的时候经常用到策略模式,比如用户会选择不同的支付方式,不同的支付方式又有不同的实现方法或银行接口调用。

[[384718]]

本文转载自微信公众号「程序新视界」,作者二师兄。转载本文请联系程序新视界公众号。   

前言

之前做三方支付系统的时候经常用到策略模式,比如用户会选择不同的支付方式,不同的支付方式又有不同的实现方法或银行接口调用。

现在做物联网系统,基于MQTT协议(TCP层面的协议)来传输数据,根据不同的请求(不同的Topic)处理不同的业务逻辑,也同样用到策略模式。

顿时感觉策略模式非常好用,而且结合Spring的实例化和注入功能,更加方便了。

今天就聊聊基于Spring(Boot)下策略模式的使用。

未使用策略模式时的处理

以物联网为例大家可能不够熟悉,下面就以支付场景为例。比如在支付的过程中我们可能会选择微信支付、支付宝支付或银卡支付。同时,银行卡又分不同银行,这里统一为银行卡。

最简单直接的代码实现形式如下:

  1. public void pay(String payType){ 
  2.     if("alipay".equals(payType)){ 
  3.         System.out.println("支付宝"); 
  4.     }else if("wechatPay".equals(payType)){ 
  5.         System.out.println("微信支付"); 
  6.     } else if("bank".equals(payType)){ 
  7.         System.out.println("银行卡支付"); 
  8.     } 

这样对照设计模式,通常不符合两个原则:单一职责原则和开闭原则。

我们会发现当前类(或方法)不处理了多个业务的功能,一旦任何一个支付方式的修改都可能会影响到其他的支付方式。同时,无法做到对扩展开放,对修改关闭。新增其他支付方式时同样要修改ifelse判断,影响到其他的业务逻辑。

而策略模式通常就是解决这种有很多ifelse处理逻辑,从而提高代码的可维护性、可扩展性和可读性。

策略模式的轮廓

在对上述代码进行改造之前,先来了解一下策略模式的基本组成。

策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。

策略模式通常由以下几部分组成:

  • Strategy策略类,用于定义所有支持算法的公共接口;
  • ConcreteStrategy具体策略类,封装了具体的算法或行为,继承于Strategy。
  • Context上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用;
  • StrategyFactory策略工厂类,用于创建策略类的具体实现;通常此部分可省略,看具体情况。比如后续实例中通过Spring的依赖注入机制实现了策略类的实例化。

用类图来表示(省略策略工厂类)如下图:

image

 

基于Spring的策略模式实现

目前在实践中通常都是基于Spring的特性来实现策略模式,这里就以此为例来进行讲解。

策略类定义

上面已经提到,策略类用于定义功能的接口,对于支付场景则可命名为PaymentService或PaymentStrategy。

  1. public interface PaymentService { 
  2.  
  3.     /** 
  4.      * 支付 
  5.      */ 
  6.     PayResult pay(Order order); 

同时提供该策略类的不同实现类:AlipayService、WeChatPayService、BankPayService。

  1. @Service("alipay"
  2. public class AlipayService implements PaymentService { 
  3.     @Override 
  4.     public PayResult pay(Order order) { 
  5.         System.out.println("Alipay"); 
  6.         return null
  7.     } 
  1. @Service("wechatPay"
  2. public class WeChatPayService implements PaymentService { 
  3.     @Override 
  4.     public PayResult pay(Order order) { 
  5.         System.out.println("WeChatPay"); 
  6.         return null
  7.     } 
  1. @Service("bank"
  2. public class BankPayService implements PaymentService { 
  3.     @Override 
  4.     public PayResult pay(Order order) { 
  5.         System.out.println("BankPay"); 
  6.         return null
  7.     } 

具体实现的实例化,可以通过一个PaymentFactory来进行构建存储,也可以直接利用@Autowired形式注入到Context的List或Map当中。

PaymentFactory的实现如下:

  1. public class PaymentFactory { 
  2.  
  3.     private static final Map<String, PaymentService> payStrategies = new HashMap<>(); 
  4.  
  5.     static { 
  6.         payStrategies.put("alipay", new AlipayService()); 
  7.         payStrategies.put("wechatPay", new WeChatPayService()); 
  8.         payStrategies.put("bank", new BankPayService()); 
  9.     } 
  10.  
  11.     public static PaymentService getPayment(String payType) { 
  12.         if (payType == null) { 
  13.             throw new IllegalArgumentException("pay type is empty."); 
  14.         } 
  15.         if (!payStrategies.containsKey(payType)) { 
  16.             throw new IllegalArgumentException("pay type not supported."); 
  17.         } 
  18.         return payStrategies.get(payType); 
  19.     } 

通过static静态代码块来初始化对应的策略实现类,然后提供一个getPayment方法,根据支付类型来获取对应的服务。当然,通过static初始化的代码块是单例的无状态的,如果需要有状态的类则getPayment方法,每次都需要new一个新的对象。

  1. public static PaymentService getPayment1(String payType) { 
  2.     if (payType == null) { 
  3.         throw new IllegalArgumentException("pay type is empty."); 
  4.     } 
  5.     if ("alipay".equals(payType)) { 
  6.         return new AlipayService(); 
  7.     } else if ("wechatPay".equals(payType)) { 
  8.         return new WeChatPayService(); 
  9.     } else if ("bank".equals(payType)) { 
  10.         return new BankPayService(); 
  11.     } 
  12.     throw new IllegalArgumentException("pay type not supported."); 

Context上下文

Context上下文角色,也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。

上面通过工厂的形式创建策略类的实现类,当然也可以直接通过@Autowired注入到Context上下文中。

  1. @Component 
  2. public class PaymentStrategy { 
  3.  
  4.     @Autowired 
  5.     private final Map<String, PaymentService> payStrategies = new HashMap<>(); 
  6.  
  7.     public PaymentService getPayment(String payType) { 
  8.         if (payType == null) { 
  9.             throw new IllegalArgumentException("pay type is empty."); 
  10.         } 
  11.         if (!payStrategies.containsKey(payType)) { 
  12.             throw new IllegalArgumentException("pay type not supported."); 
  13.         } 
  14.         return payStrategies.get(payType); 
  15.     } 

上面通过@Autowired注解,将通过@Service实例化的PaymentService实现类,注入到map当中,其中key为实例化类的名称,value为具体的实例化类。

上面的getPayment代码与PaymentFactory中一致。当然,还可以在PaymentStrategy中封装一个pay方法,这样,客户端直接注入PaymentStrategy类调用pay方法即可。

  1. public PayResult pay(String payType,Order order){ 
  2.     PaymentService paymentService = this.getPayment(payType); 
  3.     return paymentService.pay(order); 

改进方案

通过上面的代码基本上已经实现了策略模式,此时当新增加一个支付通道时,已经不用修改PaymentStrategy相关的代码,只用新增一个实现PaymentService接口的类即可。

但在接口定义这里,还是有优化空间的。比如,这里判断是通过Bean的名称来判断的,但某些情况下判断可能比较复杂或可能会同时执行多个Service。此时,就可以对PaymentService接口进行改进,新增一个检验是否支持该功能的判断方法。

  1. public interface PaymentService { 
  2.  
  3.     boolean isSupport(Order order); 
  4.  
  5.     /** 
  6.      * 支付 
  7.      */ 
  8.     PayResult pay(Order order); 

由实现类来具体实现isSupport方法,判断自己支持哪些功能。

同时,上下文类也可以进一步利用Java8提供的Steam特性进行处理:

  1. @Component 
  2. public class PaymentStrategy { 
  3.  
  4.     /** 
  5.      * 此处用@Autowired将所有实例注入为List。 
  6.      */ 
  7.     @Autowired 
  8.     private List<PaymentService> paymentServices; 
  9.  
  10.     public void pay(Order order) { 
  11.         PaymentService paymentService = paymentServices.stream() 
  12.                 .filter((service) -> service.isSupport(order)) 
  13.                 .findFirst() 
  14.                 .orElse(null); 
  15.  
  16.         if (paymentService != null) { 
  17.             paymentService.pay(order); 
  18.         } else { 
  19.             throw new IllegalArgumentException("pay type not supported."); 
  20.         } 
  21.     } 

通过进一步改造,程序变得更加灵活了。

小结

通过上面的代码实现,可以看出接口类只负责业务策略的定义,策略的具体实现可以单独放在实现类中也可以利用Spring的特性进行管理,Context上下文类负责业务逻辑的编排。

通过策略模式(或变种)的应用,实现了面向接口而非实现编程,满足了职责单一、开闭原则,从而达到了功能上的高内聚低耦合、提高了可维护性、扩展性以及代码的可读性。

 

最后,对于设计模式,只有在实践中不断的使用采用更加印象深刻。同时,在实现的过程中我们也并不一定非要拘泥于设计模式本身,也可以结合所使用的框架进行变种处理。

 

责任编辑:武晓燕 来源: 程序新视界
相关推荐

2016-03-17 13:50:17

融合通信华为

2012-03-09 15:55:05

新版

2015-08-07 13:36:49

南京软博会

2017-02-28 11:13:36

华为

2013-08-23 10:12:02

苹果iPhone 5S

2015-05-28 09:58:00

手机电池续航内存

2015-10-14 13:02:37

ios9越狱

2014-09-24 13:08:35

微信企业号

2021-11-01 22:24:08

电脑配置设置

2012-08-10 09:29:36

imo即时通讯

2020-09-23 06:52:49

代码方法模式

2022-04-26 14:14:32

数字人民币支付数字化

2023-03-16 18:30:55

GPT-4ChatGPT

2022-11-07 11:42:36

人工智能机器人无人驾驶

2021-05-10 19:17:54

GoInt128类型

2021-05-26 17:33:47

AI 数据人工智能

2022-05-31 09:42:49

工具编辑器

2024-05-11 09:38:05

React编译器React 19

2021-08-24 15:03:12

开发技能代码

2018-12-24 09:42:53

人工智能机器学习技术
点赞
收藏

51CTO技术栈公众号