策略模式是Java 23种设计模式之一,在https://refactoring.guru/design-patterns/网站中这样对策略模式进行了解释:
Strategy is a behavioral design pattern that turns a set of behaviors into objects and makes them interchangeable inside original context object.
白话翻译一下就是:策略模式可以根据上下文对象的不同状态去执行不同的逻辑(策略实现)。最简单使用场景是:当代码中出现了三重以上的if else判断,这时代码的可读性会非常差,这时可以使用到策略模式去拯救if else.当然也可以使用switch,但是相较于策略模式,switch的代码清晰度还是差了些。
加减乘除的计算器如果使用传统的if else的话,至少需要写三次if和一次else,如果使用switch的话,则在一个方法中堆叠四个case或者三个case一个default,如果每个if条件下的逻辑都特别多的话,代码会显得特别臃肿,在《阿里巴巴开发手册》中规定:一个方法中代码量不能超过80行,如果逻辑判断都挤在一个方法里,代码量一定会超过80行,ok,你的KPI完了。所以,年轻人,耗子尾汁,还不赶紧学学策略模式。
接下来我将使用加减乘除的一个小案例来写一个最简单的策略模式的demo
<!-- SpringBoot所必须的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<!-- demo中使用guava的Maps集合和判空-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
建议:新建一个strategy包,包下定义策略接口实现包strategyImpl和不同的策略角色,也就是上文中的2 ps:实际项目中传参可能不是基本类型,而是一个自定义的一个包装类型(实体类),所以入参判断可以使用@Validated注解判空,我这里是int基本类型,所以在判空的时候就使用了Google guava封装的Preconditions去判空,业务判空一定要提前,不要走到较深的业务中再去判空
1、定义策略接口,也就是上文中的1
CalculationStrategy.java
/**
* @author Liu-PC
*/
public interface CalculationStrategy {
/**
* 策略模式的策略接口定义
* @param num1 first
* @param num2 second
* @return
*/
int operate(int num1, int num2);
}
AddCalculationStrategyImpl.java
/**
* @author Liutx
* @date 2020/11/28 16:05
* @Description
*/
@Component("add")
public class AddCalculationStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 + num2;
}
}
这里的@Component是把当前策略的实现类注册为一个组件,交由Spring管理。add是策略的名字,策略的名字不能重复,因为我们策略接口通过组件的名字来找到具体的策略角色。
DivisionStrategyImpl.java
/**
* @author Liutx
* @date 2020/11/28 16:18
* @Description
*/
@Component("Division")
public class DivisionStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 / num2;
}
}
MultiplicationStrategyImpl.java
@Component("multiple")
public class MultiplicationStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 * num2;
}
}
SubtractionStrategyImpl.java
@Component("subtract")
public class SubtractionStrategyImpl implements CalculationStrategy {
@Override
public int operate(int num1, int num2) {
return num1 - num2;
}
}
CalculationContext.java
/**
* @author Liutx
* @date 2020/11/28 16:23
* @Description 策略上下文类,把传入的参数放到map中,作为策略接口的入参
*/
@Component
@Getter
public class CalculationContext {
/**
* 把策略角色(类型)key,和参数value放到Map中
* key就是beanName(具体策略实现类中@Component的名字),value就是接口(具体的实现类)
* Maps是guava下的封装类型,实则是静态的创建了一个HashMap的对象,Maps可以根据key去获取value对象
*/
public final Map<String, CalculationStrategy> calculationStrategyMap = Maps.newHashMapWithExpectedSize(4);
public CalculationContext(Map<String, CalculationStrategy> calculationStrategyMap) {
this.calculationStrategyMap.clear();
this.calculationStrategyMap.putAll(calculationStrategyMap);
}
//可以使用@Getter注解代替,这样写方便读者理解在Service层调用Context执行策略
public Map<String, CalculationStrategy> getCalculationStrategyMap() {
return calculationStrategyMap;
}
}
同样需要使用@Component,个人理解策略模式中的Context对象实例就像是一个执行者
以上,策略模式就已经写完了,下面是Controller中调用Service,Service中的Context"执行者"通过@Autowired注入的方式去获得Context对象,对象根据不同的策略角色去执行不同的策略实现。
CalculationService.java
/**
* @author Liutx
* @date 2020/11/28 16:03
* @Description Service里执行,相当于写在ServiceImpl里的业务逻辑,可以在Controller里调用
* 上下文环境角色,保存了ConcreteStrategy,负责调用ConcreteStrategy,所以就使用Context对象去执行策略
* 策略的不同实现类,就相当于if else中不同的逻辑代码
* 本demo使用加减乘除代替不同的策略逻辑
*/
@Service
public class CalculationService {
@Autowired
private CalculationContext calculationContext;
public int operateByStrategy(String strategy, int num1, int num2) {
//获取入参,根据不同的参数类型去执行不同的策略,Context的get方法是在这个地方用到的,operate方法就是一开始定义的策略接口
return calculationContext.getCalculationStrategyMap().get(strategy).operate(num1, num2);
}
}
TestController.class
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
private CalculationService calculationService;
@GetMapping("calculation")
public int testCalculation(String operation, int num1, int num2) {
//省略参数判空
return condition = calculationService.operateByStrategy(operation, num1, num2);
}
}
1-1=0
over ~ ~ ~