SpringBoot使用策略者模式

前言

在实际业务中,经常会遇到这样的代码

String type = actualService.getRealtype(uid);
if(type.equals("typeA")){
    // do func A
}else if(type.equals("typeB")){
    // do func B
}else if(type.equals("typeC")){
    // do func C
}else[
   //...
}

这种 if-else 或者 switch-case 代码在每个分支都会判断分支类型,然后执行不同的方法获取结果,当代码分支比较少并且确定不会增加时,使用这种方式也是完全 ok 的,但是当分支比较多,并且后面可能会增加分支判断条件时,这种方式就违反了单一职责和开闭原则,因此对于我们开发工作中遇到这种情况,首先想到的是应该去优化这种代码中的“坏味道”,其中的方法之一就是考虑能不能用策略模式去重写,将代码和业务逻辑解耦,这样才有利于后续的维护工作。

策略模式的定义

策略设计模式是一种行为设计模式。当在处理一个业务时,有多种处理方式,并且需要在运行时决定使哪一种具体实现时,就会使用策略模式。策略模式,简单来说就是通过实现接口来重写不同的方法,从而通过上下文自动获取选择的策略方法并执行。

策略模式结构

以下基于 SpringBoot 的依赖注入实现策略模式。假设场景如下:在支付业务中,有三种付款方式,程序运行时使用哪种方式由用户选择,根据用户选择执行不同的逻辑。

首先,我们需要将支付方式这一行为抽象为一个策略接口,代表支付方式的抽象

public interface PaymentStrategy {
    String paymentInformation(String type);
}

然后我们再针对需要支持的三种支付方式建立对应的策略实现类

微信支付策略

@Component("A")
public class PaymentA implements PaymentStrategy {
    @Override
    public String paymentInformation(String type) {
        return "A 微信支付";
    }
}

支付宝支付策略

@Component("B")
public class PaymentB implements PaymentStrategy {
    @Override
    public String paymentInformation(String type) {
        return "B 支付宝支付";
    }
}	

银行卡支付策略

@Component("C")
public class PaymentC implements PaymentStrategy {
    @Override
    public String paymentInformation(String type) {
        return "C 银行卡支付";
    }
}

然后我们需要写一个 SimpleContext 类来存储我们的策略类别,这时候就用到了 Spring 的依赖注入和自动发现。

@Service
public class PaymentContextService {

    @Autowired
    private Map<String, PaymentStrategy> strategyMap;

    public String getResource(String paymentType){
        return strategyMap.get(paymentType).paymentInformation(paymentType);
    }
    
}

接下来就是我们的实际调用场景了~,如下:

@RestController
@RequestMapping("/test")
public class TestController {
    @Autowired
    private PaymentContextService contextService;

    @GetMapping("/payment/{type}")
    public String paymentMethod(@PathVariable("type") String type){
        return contextService.paymentMethod(type);
    }

}

那么当我们的入参 type传入 “A” 时,返回的结果如下:

A 微信支付

同理,不同传参都会进入不同的策略执行方法。过这个简单的 demo,就可以看到通过获取输入不同的type,可以自动的使用不同的支付方式。
通过实践总结下来,使用策略模式的好处就是通过一个封装的上下文可以自由的切换不同的算法,省去多重判断,同时可以具有很好的扩展性。

使用枚举类来实现

@AllArgsConstructor
@Getter
public enum PaymentEnum {
    // 微信支付方式
    WECHAT("A", PaymentA.class),
    // 支付宝支付方式
    ALIPAY("B", PaymentB.class),
    // 银行卡
    BANKCARD("C", PaymentC.class);

    private String type;
    private Class<?> service;

    public static PaymentStrategy getPaymentStrategy(String type){
        for (PaymentEnum value : PaymentEnum.values()) {
            if (Objects.equals(value.getType(), type)) {
                System.out.println("value = " + value);
                System.out.println(value.getService());
                // 从容器中获取bean
                return (PaymentStrategy) SpringContextUtils.getBean(value.getService());
            }
        }
        return null;
    }
}

在业务中调用

public String paymentMethodEnum(String paymentType){
    return PaymentEnum.getPaymentStrategy(paymentType).paymentInformation(paymentType);
}

**注意:**SpringContextUtils是我自定义的一个工具类

@Component
@Slf4j
public class SpringContextUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringContextUtils.applicationContext == null) {
            SpringContextUtils.applicationContext = applicationContext;
        }
    }

    /**
     * 获取applicationContext
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    /**
     * 通过name获取 Bean
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    /**
     * 通过class获取Bean
     */
    public static <T> T getBean(Class<T> clazz) {
        System.out.println("clazz = " + clazz);
        return getApplicationContext().getBean(clazz);
    }

    /**
     * 通过name, 以及Clazz返回指定的Bean
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

总结

从上面可以看出,策略模式的优缺点十分明显,在我们实际的业务中,也需要看情况使用。

优点:

  1. 策略模式符合开闭原则
  2. 代码简洁,从上下文自动获取条件转移语句
  3. 使用策略模式可以提高算法的保密性和安全性

缺点:

  1. 每个策略都需要单独实现一个类,当策略很多时,会产生大量的策略类,会使代码出现“膨胀”
  2. 客户端必须知道所有的策略
  3. 策略模式的一系列算法地位是平等的,是可以相互替换的,事实上构成了一个扁平的算法结构,也就是在一个策略接口下,有多个平等的策略算法,就相当于兄弟算法。而且在运行时刻只有一个算法被使用,这就限制了算法使用的层级,使用的时候不能被嵌套使用

Q.E.D.

同是风华正茂,怎可甘拜下风