设计模式-策略模式集成spring的使用

1,498 阅读3分钟

Why

设计模式的七大原则是

  1. 开闭原则
  2. 依赖倒转原则
  3. 单合一职责原则
  4. 接口隔离原则
  5. 迪米特法则
  6. 里氏替换原则
  7. 复用合成原则

这些是前人总结出来的优良设计特点, 所谓见贤思齐 如果代码靠近这些原则,那么设计会散发出她的魅力。

应用场景

策略模式在生活场景中的应用很多,比如

  1. 进地铁站是使用NFC支付还是二维码支付

  2. 在移动互联网的支付背景下,下单支付时, 选择微信,支付宝还是银联支付

使用策略模式可以减少 if else或者switch case带来的复杂性与臃肿性问题

策略模式适用于以下场景:

  1. 针对同一类型问题, 有多种处理方式, 每一种都能独立解决问题
  2. 需要自由切换算法的场景
  3. 需要算法屏蔽的场景

实际问题demo

此次以我司的“消息发送”策略为例

如图所示, "数据分发"接口需要同时接入以下接口,同时开发周期并不是并行的,所以会造成新接口对已有接口的影响。 在开发过程中这种影响是及其麻烦的,开发时间紧任务重,回归测试也无法正常的进行,这对开发和测试来说都是很重的任务量

所以如何解决这个问题就成了一个症结, 是用if垒代码,还是运用设计模式解决问题。

What

这时候就需要策略模式的接入,策略模式到底做了什么完成求同存异的工作?

How

与spring集成

  1. 使用上下文工具类记录上下文

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);

    }



}

  1. 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);

    }

}



  1. 声明注解

注解的作用主要是标识策略实现类


@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

@Inherited

public @interface MessageStrategy {



    ChannelTypeEnum channelType();



}



  1. 具体实现类

所有的策略实现类,都需要使用上面的注解进行注释,同时需要对注解定义的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);
    }

}

  1. 注入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);

        });

    }

}

  1. 环境类

@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);
    }

优点

  1. 符合开闭原则

不用碰之前的代码, 不用考虑回归测试

  1. 减少条件语句

可以大量减少if else switch

这是这个设计模式的最大好处

  1. 提高算法的保密性

应该是说具体策略类相互隔离,加强了保密性

缺点

凡事有利有弊, 不可能存在全是优点的设计模式。

有时候往往它的优点就是它的缺点!!!

  1. 客户端必须知晓所有的具体策略类

体现在必须将策略实体类放入到map中,这个初始化bean耗时

  1. 会产生很多策略类,增加维护难度

这个就是代替if else的地方,直接将代码块转移到类中,做到类与接口的单一职责

我宁愿多写几个类,也不愿意加上罗里吧嗦的if else

参考资料

策略模式