一、策略模式简介
策略模式:
指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。本模式使得算法可独立于使用他的用户而变化
解决的问题:
在多种策略(算法)相似的情况下,使用if…else所带来的的复杂和难以维护
优点:
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
缺点:
- 初学者觉得设计过重
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一 定程度上减少对象的数量。
二、策略模式组成机构
- 上下文环境(Context):持有一个策略类的引用,最终给客户端使用,便于隔离客户端和算法
- 抽象策略(Strategy):策略类,通常是一个接口或抽象类
- 具体策略(ConcreteStrategy):实现了策略类中的策略方法,封装相关的算法和行为
三、策略模式UML图解
四、应用场景
- 多个类之区别在行为表现不同,可使用策略模式,在运行时动态选择具体要执行的行为
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其他方式来实现
- 对用户隐藏具体策略(算法)的实现细节,彼此完全独立
五、案例实现
测试案例只为简单的展现策略模式,其中如参各参数未进行校验,可能存在数据问题;
上下文环境:
package com.chorany.policy.service.impl;
import com.chorany.policy.service.CalculatorService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author : chorany
* @date: 2022/5/27 0:25
* @description: 策略模式上下文类,用于隔绝客户端和算法
*/
@Component
public class CalcContext {
@Autowired
private List<CalculatorService> calculatorServices;
@SneakyThrows
public CalculatorService getCalculatorServiceBean(String operationType) {
for (CalculatorService calculatorService : calculatorServices) {
//根据类型匹配相对应的实现
if (calculatorService.getOperationType().getCode().equals(operationType)) {
return calculatorService;
}
}
throw new Exception("根据操作类型,未找到相应策略");
}
}
抽象策略:
package com.chorany.policy.service;
import com.chorany.policy.CalculatorVO;
import com.chorany.policy.enums.OperationTypeEnum;
/**
* 计算类接口
*
* @author : chorany
* @date : 2022/5/23 23:50
* @description
*/
public interface CalculatorService {
public OperationTypeEnum getOperationType();
public Double Calculator(CalculatorVO calculatorVO);
}
具体策略实现:
- 加法实现
package com.chorany.policy.service.impl;
import com.chorany.policy.CalculatorVO;
import com.chorany.policy.enums.OperationTypeEnum;
import com.chorany.policy.service.CalculatorService;
import org.springframework.stereotype.Service;
/**
* 加法运算类
*
* @author : chorany
* @date : 2022/5/23 23:50
* @description
*/
@Service
public class AddCalculatorProcess implements CalculatorService {
@Override
public OperationTypeEnum getOperationType() {
return OperationTypeEnum.ADD;
}
@Override
public Double Calculator(CalculatorVO calculatorVO) {
return calculatorVO.getFirstNum() + calculatorVO.getSecondNum();
}
}
- 减法实现
package com.chorany.policy.service.impl;
import com.chorany.policy.CalculatorVO;
import com.chorany.policy.enums.OperationTypeEnum;
import com.chorany.policy.service.CalculatorService;
import org.springframework.stereotype.Service;
/**
* 减法运算类
*
* @author : chorany
* @date : 2022/5/23 23:50
* @description
*/
@Service
public class SubCalculatorProcess implements CalculatorService {
@Override
public OperationTypeEnum getOperationType() {
return OperationTypeEnum.SUB;
}
@Override
public Double Calculator(CalculatorVO calculatorVO) {
return calculatorVO.getFirstNum() - calculatorVO.getSecondNum();
}
}
- 乘法实现
package com.chorany.policy.service.impl;
import com.chorany.policy.CalculatorVO;
import com.chorany.policy.enums.OperationTypeEnum;
import com.chorany.policy.service.CalculatorService;
import org.springframework.stereotype.Service;
/**
* 乘法运算类
*
* @author : chorany
* {@code @date} : 2022/5/23 23:50
* @ description
*/
@Service
public class MulCalculatorProcess implements CalculatorService {
@Override
public OperationTypeEnum getOperationType() {
return OperationTypeEnum.MUL;
}
@Override
public Double Calculator(CalculatorVO calculatorVO) {
return calculatorVO.getFirstNum() * calculatorVO.getSecondNum();
}
}
- 除法实现
package com.chorany.policy.service.impl;
import com.chorany.policy.CalculatorVO;
import com.chorany.policy.enums.OperationTypeEnum;
import com.chorany.policy.service.CalculatorService;
import org.springframework.stereotype.Service;
/**
* 除法运算类
*
* @author : chorany
* @date : 2022/5/23 23:50
* @description
*/
@Service
public class DivCalculatorProcess implements CalculatorService {
@Override
public OperationTypeEnum getOperationType() {
return OperationTypeEnum.DIV;
}
@Override
public Double Calculator(CalculatorVO calculatorVO) {
return calculatorVO.getFirstNum() / calculatorVO.getSecondNum();
}
}
操作类型枚举类:
package com.chorany.policy.enums;
/**
* @author : chorany
* @date : 2022/5/23 23:27
* @description :
*/
public enum OperationTypeEnum {
ADD("ADD", "加法"),
SUB("SUB", "减法"),
MUL("MUL", "乘法"),
DIV("DIV", "除法"),
;
private String code;
private String name;
OperationTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ControllerD调用测试类及VO类:
vo类:
package com.chorany.policy.vo;
import lombok.Data;
/**
* 请求入参VO
*
* @author : chorany
* @date : 2022/5/23 23:46
* @description
*/
@Data
public class CalculatorVO {
private Double firstNum;
private Double secondNum;
private String operationType;
}
测试接口类:此处采用多态方式引用当前策略的所有实现类
package com.chorany.policy.controller;
import com.chorany.policy.service.impl.CalcContext;
import com.chorany.policy.vo.CalculatorVO;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author : chorany
* @date : 2022/5/23 23:22
* @description
*/
@RestController
@RequestMapping("/v1/calc")
public class CalculatorController {
@Autowired
private CalcContext calcContext;
/**
* 使用上下文类对客户端和算法进行隔离,其实就是相当于把上面匹配的代码挪到上下文类中,我喜欢使用上面一种方式,学习起来更方便,看前来更简洁
*
* @param calculator
* @return
*/
@PostMapping("/testByContext")
public Double CalculatorByContext(@RequestBody CalculatorVO calculator) {
return calcContext.getCalculatorServiceBean(calculator.getOperationType()).Calculator(calculator);
}
}
六、个人总结
在实际开发中策略模式应用十分广泛,我们可以根据自己开发时的需要对策略模式进行稍微变更,例如:可结合工厂模式在上下文类中动态创建策略实现类等,我个人不喜欢在客户端和算法之间增加上下文类,因为这样spring容器中相当于多创建另一个bean,增加管理成本
策略模式可省略上下文类,采用多态方式引用当前策略(算法)的实现类集合
省略上下文类,直接引用策略(算法)实现类集合(这种方式可省略上下文类)
package com.chorany.policy.controller;
import com.chorany.policy.vo.CalculatorVO;
import com.chorany.policy.service.CalculatorService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author : chorany
* @date : 2022/5/23 23:22
* @description
*/
@RestController
@RequestMapping("/v1/calc")
public class CalculatorController {
@Autowired
private List<CalculatorService> calculatorService;
/**
* 初始化bean的时候采用集合注入所有接口实现类的方式
*
* @param calculator
* @return
*/
@SneakyThrows
@PostMapping("/test")
public Double Calculator(@RequestBody CalculatorVO calculator) {
//根据传入计算类型,匹配上就执行相对应的处理方式
for (CalculatorService service : calculatorService) {
if (service.getOperationType().getCode().equals(calculator.getOperationType())) {
return service.Calculator(calculator);
}
}
throw new Exception("根据操作类型,未找到相应策略");
}
}