策略模式

基本介绍

1)策略模式(StrategyPattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
2)这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
说明:从上图可以看到,客户context有成员变量strategy或者其他的策略接口,至于需要使用到哪个策略,我们可以在构造器中指定

策略模式优化IF else

类图

image.png
使用策略模式+工厂+模板方法 解决多分类查询

  1. 浏览器调用Controller, Controller调用Service, Service调用查询工厂
  2. 在WarningQueryFactory中维护查询Key和具体查询的的关系, 并实现ApplicationContextAware接口获取到IOC, 然后通过IOC获取抽象查询类调用(AbstractWarningQuery, 并调用抽象查询类的模板方法
  3. 在WaringQuery接口中定义统一查询方法
  4. 使用AbstractWaringQuery对其进行实现, 并在其中定义模板方法, 并且在模板方法中调用抽象接口
  5. 具体查询实现类继承抽象查询类, 并实现WarningQuery接口中的查询方法, 同时注册到IOC中, 可以让工厂从IOC中获取到

这是一个标准的策略+模板的实现. 本来我想在其中加入状态模式, 用于控制是根据一些参数, 来决定查询DB还是查询缓存, 但是后来应为一些场景是缓存实现不了的, 只能查DB了, 但是一些公用数据还是查询缓存的, 后续如果需要扩展其他查询, 只需要在工厂层中维护映射关系, 并添加新的实现类继承抽象查询类(AbstractWariningQuery), 并实现WarningQuery接口的查询方法即可

代码实现

Controller层

  1. @GetMapping("/queryWarningByKeyPage")
  2. public Object queryWarningByKey(@Valid WarningQueryParam warningQueryParam) {
  3. return warningService.queryWarningByKey(warningQueryParam);
  4. }

Service层

  1. /**
  2. * 根据配置决定查询数据库还是缓存默认为数据库,缓存还有问题(2022/5/12 已修复)
  3. *
  4. * @param warningQueryParam 预警查询条件
  5. * @return 数据
  6. */
  7. public Object queryWarningByKey(WarningQueryParam warningQueryParam) {
  8. String key = warningQueryParam.getKey();
  9. String deptName = warningQueryParam.getDeptName();
  10. Long pageNum = warningQueryParam.getPageNum();
  11. Long pageSize = warningQueryParam.getPageSize();
  12. return warningQueryFactory.queryDataByKey(key, deptName, pageNum, pageSize);
  13. }

工厂

  1. @Component
  2. public class WarningQueryFactory implements ApplicationContextAware {
  3. public static final ConcurrentHashMap<String, String> context = new ConcurrentHashMap<>();
  4. private ApplicationContext applicationContext;
  5. static {
  6. // context.put("021","opwWarningQuery");
  7. context.put("022","opwWarningQuery");
  8. String videoWarning = "videoWarningQuery";
  9. context.put("011",videoWarning);
  10. }
  11. public Object queryDataByKey(String key,String deptName,Long pageNum, Long pageSize){
  12. if("021".equals(key)){
  13. throw new BusinessException("该类型接口暂未实现, 敬请期待!");
  14. }
  15. String s = context.get(key);
  16. AbstractWaringQuery bean = applicationContext.getBean(s, AbstractWaringQuery.class);
  17. return bean.queryWarningByKeyPageBase(key,deptName,pageNum,pageSize);
  18. }
  19. @Override
  20. public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
  21. this.applicationContext = applicationContext;
  22. }
  23. }

高层查询接口

public interface WaringQuery {
    Object queryWaringByKeyPage(String key,String deptName, Long pageNum, Long pageSize);
}

抽象实现者

public abstract class AbstractWaringQuery implements WaringQuery {

    public Object queryWarningByKeyPageBase(String key,String deptName,Long pageNum, Long pageSize){
        if(pageNum == null || pageNum < 1){
            pageNum = 1L;
        }
        if(pageSize == null || pageSize < 1){
            pageSize = 4L;
        }
        return queryWaringByKeyPage(key,deptName,pageNum,pageSize);
    }
}

具体实现者OPW

@Component
public class OpwWarningQuery extends AbstractWaringQuery {
    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private DeptService deptService;

    @Autowired
    private PressureService pressureService;

    @Value("${query.warning.type}")
    private String queryType;

    @Autowired
    private UserContextService userContextService;

    @Autowired
    private OnlineService onlineService;

    @Override
    public CommonPage<List<PressureVo>> queryWaringByKeyPage(String key, String deptName, Long pageNum, Long pageSize) {
        // 暂时不支持Redis了
//        if (QueryType.REDIS.equals(queryType)) {
//            return queryDataByRedis(key, pageNum, pageSize);
//        }
        return queryDateByDb(key,deptName, pageNum, pageSize);
    }

    private CommonPage<List<PressureVo>> queryDateByDb(String key,String deptName, Long pageNum, Long pageSize) {
        // 业务逻辑
    }

    private CommonPage<List<PressureVo>> queryDataByRedis(String key, Long pageNum, Long pageSize) {
        // 业务逻辑
    }

    private List<PressureVo> addIsOnline(List<Pressure> records, List<String> deptCodes) {
        // 业务逻辑
    }
}

策略模式的注意事项和细节

1)策略模式的关键是:分析项目中变化部分与不变部分
2)策略模式的核心思想是:多用组合/聚合少用继承;用行为类组合,而不是行为的继承。更有弹性
3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if..elseif..else)
4)提供了可以替换继承关系的办法:策略模式将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展
5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞