关于策略模式的一些实践

246 阅读4分钟

最近公司想整合公司已有的各个支付方式,做到所有的支付请求调用统一的支付面板执行同一个接口,但是由于公司现有的支付方式有扫描二维码支付、pos机支付、钱包虚拟卡支付、实体卡支付等等,所以现在要对各种支付方式进行整合。

遇到的问题

  1. 由于各个支付都拥有自己的支付逻辑所以导致每个支付方式的入参都不一样,需要做到入参的统一
  2. 整合多个支付方式意味着需要在控制层去适配每一种支付,怎么做才能使得代码可维护性强拓展性强
  • 对于问题一,首先最容易想到的实现方式就是,冗余所有入参的字段整合到一个Javabean里面去,然后在针对不同的支付方式提取出自己所需的字段。但是这么做会导致代码的可读性很差不容易维护,同时也使得各类支付方式的耦合在一起不利于系统得维护与Spring所推崇得高内聚、低耦合相左。所以在这里我选则使用序列化和反序列化去解决这个问题。在执行请求得时候统一先使用Object接受前段传入得参数,然后在真正执行业务逻辑得时候再去将数据反序列化成实际业务的入参。

前端参数传入格式

{
    //支付方式
    "type":""
    //不同支付方式的传入参数 
    "param":{
        ....
}

控制层代码

    public Response pay(@RequestBody Object param) throws ClassNotFoundException{
    //将将传入的参数对象转型成Map
     Map<String,Object> paramMap = (Map<String,Object>)param;
    //将入参序列化成json字符串
    String clazz = paramMap.get("param");
    //根据传入的type执行对应的策略
    response.setData(strategyService.pay((String)paramMap.get("type"),JSON.toJSONString(clazz)));
     //设置返回值
    Response response = new Response();
    return response;
} 
  • 对于问题二,同样的最先想到的是使用if-else去判断然后执行不同的支付逻辑。在支付方式不多的情况下这么做是没有什么问题的,但是如果支付方式很多就会使的判断逻辑过长,而且再新接入一种支付方式的时候需要考虑与已有的支付逻辑是否冲突彼此之间不独立,这样一来代码可维护性变差而且拓展起来麻烦。所以在这里我使用策略模式来做多种支付方式的适配
  • 关于策略模式

我们首先先定义一个策略的接口定义策略的执行方法,然后实现该接口得到相应策略的具体执行。然后使用自定义注解,在项目启动时通过反射的方式将所有的策略注入策略容器中。最后使用PayTypeContext通过传入的支付方式(type参数)定位该策略在策略容器中的位置并得到对应的策略类执行相关业务方法。使用策略模式的好处是当我需要接入一种新的支付方式时,我只需要新建一策略即可,而且各个策略之间相互独立,这样就让我们在接入新的支付方式的时候只需要关注业务本身就行了。

策略类接口
public interface Strategy {
/**
 * 定义策略的执行方法
 * @return
 */
String pay(String type,String clazz);
}
策略实现类
@Service
@PayType("pos")
public class StrategyA implements Strategy {
    @Override
    public String pay(String type,String clazz) {
        //反序列化,得到执行业务的具体参数
        Pos pos = JSON.parseObject(Pos, A.class);
        // 执行业务逻辑 service.pay(pas)
        return "";
    }
}
自定义注解:

用来定位对应的策略类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PayType {

    String value() default "";

}
策略容器
@Component
public class AllPayType implements ApplicationRunner {

/**
 * 策略容器
 */
private final HashMap<String,Class> ALL_PAY_SOURCE = new HashMap<>(100);

public  Class getPayType(String type){
    return ALL_STRING_SOURCE.get(type);
}

public  void initAllDS(String packageName){
    URL url = this.getClass().getClassLoader().getResource(packageName.replace(".","/"));
    if(url != null) {
       File dir = new File(url.getFile());
       for (File file : Objects.requireNonNull(dir.listFiles())) {
           if (file.isDirectory()) {
               // 递归扫描子目录
               initAllDS(packageName);
           } else {
               String clazzName = packageName + "." + file.getName().replace(".class", "");
               try {
                   Class<?> clazz = Class.forName(clazzName);
                   if (clazz.isAnnotationPresent(PayType.class)) {
                       // 获取策略名
                       PayType annotation = clazz.getAnnotation(PayType.class);
                       // 将扫描到的策略放入容器
                       ALL_PAY_SOURCE.put(annotation.value(), clazz);
                   }
               } catch (ClassNotFoundException e) {
                   e.printStackTrace();
               }
           }
       }
   }

}
@Override
public void run(ApplicationArguments args) throws Exception {
     this.initAllDS("com.fei.strategy");
}
}    
通过传入的支付类型生成对应的策略
@Component
public class PayTypeContext {
@Autowired
private ApplicationContext applicationContext;

/**
 *  注入策略容器
 */
@Autowired
private AllPayType AllPayType;

public Strategy getStrategyInstance(String type) throws ClassNotFoundException {
    Class<?> clazz = AllPayType.getPayType(type);
    // 获取策略实例
    Strategy strategyInstance = ((Strategy) applicationContext.getBean(clazz));
    return strategyInstance;

}

}
service
    @Service
    public class StrategyServiceImpl implements StrategyService {

    @Autowired
    private PayTypeContext PayTypeContext;

    @Override
    public String getPay(String type,String clazz) throws ClassNotFoundException{
        return PayTypeContext.getStrategyInstance(type).pay(type,clazz);
    }
}