策略模式
策略模式就是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换;它可以替换继承关系,避免使用多重条件转移语句
典型应用场景
- 根据不同的类型选择不同的计费策略、处理机制
参考:https://blog.csdn.net/u010247622/article/details/106220318/
- 容错恢复机制,当程序出现错误时,使用备选方案
参考:https://www.cnblogs.com/LoveShare/p/10953940.html
算法结构
如图,该算法,一般先定义算法接口,然后根据不同的算法策略实现具体的算法;然后将算法注入到StrategyContext中,之后Client根据不同的类型获取对应的算法,执行之。
使用举例
公交计费时可以使用多种方式,常见的有现金、公交卡、电子公交卡、NFC等,每种方式计价的方式不同,比如:
- 现金不打折
- 公交卡打85折
- 电子公交卡打9折
- NFC打8折
现在需要实现公交计费。
典型的方式是使用if else判断不同场景进行计算,但是扩展性差,代码也不美观。
现在采用自定义注解结合策略模式实现计费,代码接口如下:
首先定义个枚举类,列举主要的业务类型
public enum CalculateTypeEnum {
NFC("nfc"),
CARD("card"),
E_CARD("eCard"),
CASH("cash");
private String type;
CalculateTypeEnum(String type){
this.type=type;
}
public String getType(){
return type;
}
}
然后自定义一个注解@CalculateStrategy,该注解主要用于后面的具体算法上,作为标识使用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CalculateStrategy {
CalculateTypeEnum value() default CalculateTypeEnum.CASH;
}
实现策略模式最主要的是算法之间具有可替换性,这样的话需要定义算法接口,具体的算法实现了相同的接口,就具备了可替换性,接口定义如下:
public interface ICalculateStrategy {
Double calculateAmount(Double oriAmount);
}
分别先现金、实体公交卡、电子公交卡、NFC的计算策略
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.CASH)
public class CashStrategy implements ICalculateStrategy{
@Override
public Double calculateAmount(Double oriAmount) {
log.info("Using CashStrategy");
return oriAmount;
}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.CARD)
public class CardStrategy implements ICalculateStrategy{
@Override
public Double calculateAmount(Double oriAmount) {
log.info("Using CardStrategy");
return oriAmount*0.9;
}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.E_CARD)
public class ECardStrategy implements ICalculateStrategy {
@Override
public Double calculateAmount(Double oriAmount) {
log.info("Using ECardStrategy");
return oriAmount * 0.85;
}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.NFC)
public class NFCStrategy implements ICalculateStrategy{
@Override
public Double calculateAmount(Double oriAmount) {
log.info("Using NFCStrategy");
return oriAmount*0.8;
}
}
可以看到上面通过@Service注解将算法注入到IOC容器,使用@CalculateStrategy注解标识了不同类型的计费算法策略,现在需要将策略统一放在一个上下文中进行管理
@Component
public class CalculateStrategyContext implements InitializingBean {
/**
* 算法策略map
*/
private static Map<String, Class<ICalculateStrategy>> strategyMap;
/**
* 算法策略注入map
*
* @throws Exception 异常
*/
@Override
public void afterPropertiesSet() throws Exception {
//从IOC中获取所有加了@CalculateStrategy注解的类
Map<String, Object> beansMap = AppContextUtil.appContext.getBeansWithAnnotation(CalculateStrategy.class);
// 将其按照注解上的type放入map中
beansMap.forEach((k, v) -> {
Class<ICalculateStrategy> strategyClass = (Class<ICalculateStrategy>) v.getClass();
String type = strategyClass.getAnnotation(CalculateStrategy.class).value().getType();
if (Objects.isNull(CalculateStrategyContext.strategyMap)) {
CalculateStrategyContext.strategyMap = new HashMap<>();
}
CalculateStrategyContext.strategyMap.put(type, strategyClass);
});
}
/**
* 根据类型获取策略
*
* @param type 类型
* @return 策略
*/
public static ICalculateStrategy getStrategyByType(String type) {
Class<ICalculateStrategy> strategyClass = strategyMap.get(type);
if (Objects.isNull(strategyClass)) {
throw new RuntimeException("No strategy exists for " + type);
}
return (ICalculateStrategy) AppContextUtil.getBean(strategyClass);
}
}
工具类AppContextUtil
@Component
public class AppContextUtil implements ApplicationContextAware {
/**
* 应用上下文
*/
public static ApplicationContext appContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
AppContextUtil.appContext = applicationContext;
}
public static Object getBean(Class<?> clz) {
return appContext.getBean(clz);
}
}
最后编写测试用例测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyBatisApplication.class)
public class CalculateStrategyContextTest {
@Before
public void before() throws Exception {
}
@After
public void after() throws Exception {
}
/**
* Method: getStrategyByType(String type)
*/
@Test
public void testGetStrategyByType() throws Exception {
Double amount = 1000d;
ICalculateStrategy cashStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.CASH.getType());
System.out.println("cashStrategy:" + cashStrategy.calculateAmount(amount));
ICalculateStrategy cardStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.CARD.getType());
System.out.println("cardStrategy:" + cardStrategy.calculateAmount(amount));
ICalculateStrategy eCardStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.E_CARD.getType());
System.out.println("eCardStrategy:" + eCardStrategy.calculateAmount(amount));
ICalculateStrategy nfcStrategy = CalculateStrategyContext.getStrategyByType(CalculateTypeEnum.NFC.getType());
System.out.println("nfcStrategy:" + nfcStrategy.calculateAmount(amount));
}
}
运行结果