策略模式
策略模式就是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换;它可以替换继承关系,避免使用多重条件转移语句
典型应用场景
- 根据不同的类型选择不同的计费策略、处理机制
参考: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{@Overridepublic Double calculateAmount(Double oriAmount) {log.info("Using CashStrategy");return oriAmount;}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.CARD)
public class CardStrategy implements ICalculateStrategy{@Overridepublic Double calculateAmount(Double oriAmount) {log.info("Using CardStrategy");return oriAmount*0.9;}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.E_CARD)
public class ECardStrategy implements ICalculateStrategy {@Overridepublic Double calculateAmount(Double oriAmount) {log.info("Using ECardStrategy");return oriAmount * 0.85;}
}
@Service
@Slf4j
@CalculateStrategy(CalculateTypeEnum.NFC)
public class NFCStrategy implements ICalculateStrategy{@Overridepublic 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 异常*/@Overridepublic 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;@Overridepublic 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 {@Beforepublic void before() throws Exception {}@Afterpublic void after() throws Exception {}/*** Method: getStrategyByType(String type)*/@Testpublic 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));}}
运行结果