Why
设计模式的七大原则是
- 开闭原则
- 依赖倒转原则
- 单合一职责原则
- 接口隔离原则
- 迪米特法则
- 里氏替换原则
- 复用合成原则
这些是前人总结出来的优良设计特点, 所谓见贤思齐 如果代码靠近这些原则,那么设计会散发出她的魅力。
应用场景
策略模式在生活场景中的应用很多,比如
-
进地铁站是使用NFC支付还是二维码支付
-
在移动互联网的支付背景下,下单支付时, 选择微信,支付宝还是银联支付
使用策略模式可以减少 if else或者switch case带来的复杂性与臃肿性问题
策略模式适用于以下场景:
- 针对同一类型问题, 有多种处理方式, 每一种都能独立解决问题
- 需要自由切换算法的场景
- 需要算法屏蔽的场景
实际问题demo
此次以我司的“消息发送”策略为例
如图所示, "数据分发"接口需要同时接入以下接口,同时开发周期并不是并行的,所以会造成新接口对已有接口的影响。 在开发过程中这种影响是及其麻烦的,开发时间紧任务重,回归测试也无法正常的进行,这对开发和测试来说都是很重的任务量
所以如何解决这个问题就成了一个症结, 是用if垒代码,还是运用设计模式解决问题。
What
这时候就需要策略模式的接入,策略模式到底做了什么完成求同存异的工作?
How
与spring集成
- 使用上下文工具类记录上下文
public class SpringContextUtils {
private static ApplicationContext applicationContext;
public static void setApplicationContext(ApplicationContext applicationContext) {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 获取所有带有特定注解的bean
*
* @param annotationType 注解类型
* @param <T> bean类型
* @return bean名称-bean映射
*/
@SuppressWarnings("unchecked")
public static <T> Map<String, T> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
return (Map<String, T>) applicationContext.getBeansWithAnnotation(annotationType);
}
}
- springboot启动类保存上下文
public class Application implements ApplicationContextAware {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.setApplicationContext(applicationContext);
}
}
- 声明注解
注解的作用主要是标识策略实现类
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface MessageStrategy {
ChannelTypeEnum channelType();
}
- 具体实现类
所有的策略实现类,都需要使用上面的注解进行注释,同时需要对注解定义的channelType进行定义
短信具体实现策略类
@MessageStrategy(channelType = ChannelTypeEnum.SMS)
@Component
public class SmsIHandler extends MessageIHandler<CommonMessageDTO> {
@Autowired
MessageReqService messageReqService;
@Autowired
RocketMQTemplate rocketMQTemplate;
@Autowired
MessageReqDetailService messageReqDetailService;
@Transactional
@Override
public void sendMessage(CommonMessageDTO commonMessageDTO) {
commonMessageDTO.setTenantId(TenantStore.getTenantId());
Long messageId = messageReqService.saveCommonMessageReq(commonMessageDTO);
messageReqDetailService.saveSmsRecord(commonMessageDTO,messageId);
commonMessageDTO.setMessageId(messageId);
GenericMessage<CommonMessageDTO> mqMessage = new GenericMessage<>(commonMessageDTO);
rocketMQTemplate.syncSend(MQEnvIsolationUtils.assembleDestination(MqConstant.SEND_COMMON_MESSAGE_TO_CENTER_TOPIC),mqMessage,6000);
}
}
邮件具体实现策略
@MessageStrategy(channelType = ChannelTypeEnum.EMAIL)
@Component
public class EmailIHandler extends MessageIHandler<CommonMessageDTO> {
@Autowired
RocketMQTemplate rocketMQTemplate;
@Autowired
MessageReqDetailService messageReqDetailService;
@Autowired
MessageReqService messageReqService;
@Autowired
private TenantInfoService tenantInfoService;
@Override
public void sendMessage(CommonMessageDTO commonMessageDTO) {
commonMessageDTO.setTenantId(TenantStore.getTenantId());
Long messageId = messageReqService.saveCommonMessageReq(commonMessageDTO);
commonMessageDTO.setMessageId(messageId);
messageReqDetailService.saveEmailRecord(commonMessageDTO,messageId);
GenericMessage<CommonMessageDTO> mqMessage = new GenericMessage<>(commonMessageDTO);
rocketMQTemplate.syncSend(MQEnvIsolationUtils.assembleDestination(MqConstant.SEND_COMMON_MESSAGE_TO_CENTER_TOPIC),mqMessage,6000);
}
}
- 注入map中
springboot启动时
将所有策略实现类注入到一个map中
@Component
public class EventInitRunner implements CommandLineRunner {
/**
* 将实体类放入map中,使用时再取出来
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
Map<String, Object> beansWithAnnotation = SpringContextUtils.getBeansWithAnnotation(MessageStrategy.class);
beansWithAnnotation.values().forEach(a ->{
MessageStrategy annotation = a .getClass().getAnnotation(MessageStrategy.class);
MessageHandlerContext.map.put(annotation.channelType().getCodeStr(), a);
});
}
}
- 环境类
@Component
public class MessageHandlerContext {
public static Map<String,Object> map = new HashMap<>();
public static void exe(String code, Object params){
MessageIHandler handler = (MessageIHandler) map.get(code);
handler.sendMessage(params);
}
}
使用
map中的key是消息类型枚举的code, 所以只要指定code,就可以获得对应的实现类进行策略分发
@Override
public void sendMessage(CommonMessageDTO commonMessageDTO) {
MessageConfPO messageConf = this.checkMessageConf(commonMessageDTO);
//直接调用环境类的exe方法, 并提供参数即可
MessageHandlerContext.exe(messageConf.getChannelCode(),commonMessageDTO);
}
优点
- 符合开闭原则
不用碰之前的代码, 不用考虑回归测试
- 减少条件语句
可以大量减少if else switch
这是这个设计模式的最大好处
- 提高算法的保密性
应该是说具体策略类相互隔离,加强了保密性
缺点
凡事有利有弊, 不可能存在全是优点的设计模式。
有时候往往它的优点就是它的缺点!!!
- 客户端必须知晓所有的具体策略类
体现在必须将策略实体类放入到map中,这个初始化bean耗时
- 会产生很多策略类,增加维护难度
这个就是代替if else的地方,直接将代码块转移到类中,做到类与接口的单一职责
我宁愿多写几个类,也不愿意加上罗里吧嗦的if else