1. 程式人生 > 其它 >06-10-設計模式 策略模式

06-10-設計模式 策略模式

策略模式

基本介紹

1)策略模式(StrategyPattern)中,定義演算法族(策略組),分別封裝起來,讓他們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶

2)這演算法體現了幾個設計原則,第一、把變化的程式碼從不變的程式碼中分離出來;第二、針對介面程式設計而不是具體類(定義了策略介面);第三、多用組合/聚合,少用繼承(客戶通過組合方式使用策略)。

說明:從上圖可以看到,客戶context有成員變數strategy或者其他的策略介面,至於需要使用到哪個策略,我們可以在構造器中指定

策略模式優化IF else

類圖

使用策略模式+工廠+模板方法 解決多分類查詢

  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層

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

Service層

/**
* 根據配置決定查詢資料庫還是快取預設為資料庫,快取還有問題(2022/5/12 已修復)
*
* @param warningQueryParam 預警查詢條件
* @return 資料
*/
public Object queryWarningByKey(WarningQueryParam warningQueryParam) {
    String key = warningQueryParam.getKey();
    String deptName = warningQueryParam.getDeptName();
    Long pageNum = warningQueryParam.getPageNum();
    Long pageSize = warningQueryParam.getPageSize();
    return warningQueryFactory.queryDataByKey(key, deptName, pageNum, pageSize);
}

工廠

@Component
public class WarningQueryFactory implements ApplicationContextAware {

    public static final ConcurrentHashMap<String, String> context = new ConcurrentHashMap<>();

    private ApplicationContext applicationContext;

    static {
//        context.put("021","opwWarningQuery");
        context.put("022","opwWarningQuery");
        String videoWarning = "videoWarningQuery";
        context.put("011",videoWarning);
    }

    public Object queryDataByKey(String key,String deptName,Long pageNum, Long pageSize){
        if("021".equals(key)){
            throw new BusinessException("該型別介面暫未實現, 敬請期待!");
        }
        String s = context.get(key);
        AbstractWaringQuery bean = applicationContext.getBean(s, AbstractWaringQuery.class);
        return bean.queryWarningByKeyPageBase(key,deptName,pageNum,pageSize);
    }

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

高層查詢介面

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)需要注意的是:每新增一個策略就要增加一個類,當策略過多是會導致類數目龐