策略模式在業務中的實際應用

一個程式設計師的成長發表於2021-11-01

策略模式結構圖

策略模式主要由以上三個身份組成,這裡我們就不過多介紹策略模式的基礎知識,預設大家已經對策略模式已經有了一個基礎的認識。

業務需求

現有一個廣告點選資料埋點上報的需求,上報的埋點資料根據點選的廣告位置不同做區分進行上報,每個廣告位置的資料進行分表儲存。(eg:這裡大家也不必深究分表儲存為什麼要這麼做,我們只聊策略模式的實際應用)

程式碼實現

由於是實戰案例,那麼我們是基於SpringBoot框架的,主要要使用的Spring的一些功能,所以大家要注意。

第一步:定義策略類

首先我們定義一個上報的介面

public interface AdvertisingDataReported {

    String advertisingDataReported(Object param);
}

第二步:定義具體的策略實現類

@Service
public class BottomAdvertisingDataReported implements AdvertisingDataReported {
    
    @Override
    public String advertisingDataReported(Object param) {
      	// 具體的業務邏輯略
        return null;
    }
}

第三步:策略控制類

由於策略模式有好多具體的具體策略實現,那麼到底使用哪一個策略需要根據我們的入參,也就是我們業務中的廣告型別進行判斷,那麼我們該如何優雅的進行判斷呢?

我們先看看這種方式

public static void main(String[] args) {
        
  String advertisingType = "1";

  if (advertisingType.equals("1")) {
    // 執行策略A
  } else if (advertisingType.equals("2")) {
    // 執行策略2
  }
}

這麼寫的大有人在,我們這裡也不討論這些問題。我們先看一下這麼寫存在哪些問題?

存在的問題:

1. 違反開閉原則,每次增加新的策略實現類,都要加一個if判斷;
2. 隨著策略實現類的增加,程式碼變的臃腫,越來越難以維護;

基於這種情況,我們可不可以在專案啟動的時候,將所有的策略實現類進行初始化,儲存在Map當中,廣告型別作為key,實現類作為Value,我們看如下程式碼:

@Component
public class StrategyFactory implements ApplicationContextAware {

    private final Map<String, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      	// 返回該介面所有的實現類
        Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class);
        tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.getClass().getName(), strategyService));
    }

    public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(Class<T> clazz) {
        return STRATEGY_MAP.get(clazz.getName());
    }
}

我們的策略控制類實現了ApplicationContextAware,這個類你可以這麼理解,它可以獲得ApplicationContext的上下文,由於我們是至於SpringBoot講這個案例的,我們的策略類實現類都加了@Service註解注入到了Spring容器中,所以我們可以直接從容器中,取到策略類的所有實現類。

獲取到所有的策略實現類之後,我們把類路徑作為key,類的實現作為value儲存到了map中,到此我當時覺得就大功告成了。

大家覺得還存在什麼問題?

我們怎麼知道這個入參需要走哪個具體的策略類呢?還需要定義一個單獨的類,來對廣告型別和策略類進行對映,那這跟判斷不又是同一個邏輯的嗎?還得一直維護這個對映關係。

改造

如果不想單獨的定義一個類對廣告型別和策略類進行一一對映,那麼我們可不可以在策略類中進行解決,每個策略類實現類知道它要處理哪種型別,這樣我們就可以把map中Key類路徑的值替換為廣告型別,這樣就可以根據上報介面入參的廣告型別,直接從Map中進行get。

具體的實現有兩種,你可以自定義註解,通過加註解的方式進行區分,也可以使用方法,那麼我們這裡直接使用方法進行處理。

改造後的程式碼:

策略類:

public interface AdvertisingDataReported {

  	// 新增方法
    AdvertisingTypeEnum advertisingType();

    String advertisingDataReported(Object param);
}

策略實現類:

@Service
public class BottomAdvertisingDataReported implements AdvertisingDataReported {

    @Override
    public AdvertisingTypeEnum advertisingType() {
        return AdvertisingTypeEnum.BOTTOM;
    }

    @Override
    public String advertisingDataReported(Object param) {
        return null;
    }
}

策略控制類:

@Component
public class StrategyFactory implements ApplicationContextAware {

  	// Map的Key改為廣告型別列舉類
    private final Map<AdvertisingTypeEnum, AdvertisingDataReported> STRATEGY_MAP = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, AdvertisingDataReported> tempMap = applicationContext.getBeansOfType(AdvertisingDataReported.class);
        tempMap.values().forEach(strategyService -> STRATEGY_MAP.put(strategyService.advertisingType(), strategyService));
    }

  	// 根據廣告型別獲取相應的策略類
    public <T extends AdvertisingDataReported> AdvertisingDataReported getInstance(AdvertisingTypeEnum advertisingTypeEnum) {
        return STRATEGY_MAP.get(advertisingTypeEnum);
    }
}

廣告列舉類:

public enum AdvertisingTypeEnum {

    BOTTOM, TOP;

    private String advertisingType;

    AdvertisingTypeEnum() {}
  
  	// set get省略
}

策略類的具體使用

@RestController
public class AdvertisingDataReportedController {

    @Resource
    private StrategyFactory strategyFactory;

    @RequestMapping(value = "/reported/data", method = RequestMethod.POST)
    public String reportedData(AdvertisingTypeEnum advertisingTypeEnum, Object obj) {

        AdvertisingDataReported dataReported = strategyFactory.getInstance(advertisingTypeEnum);

        String result = dataReported.advertisingDataReported(obj);

        return "SUCCESS";
    }
}

小小總結:

到這裡我們這個策略模式的案例就算結束了,有幾個問題不知道大家有沒有疑惑,為什麼我要用Object作為方法的入參,我們這種案例中,好像每個策略類的入參好像都是一樣的,但是也有可能出現同一個策略的實現類,但是入參完全可能不相同,那麼這個時候,我們就可以通過傳入Object的方式,在方法內部進行轉換,當然了,如果這樣你嫌策略方法太死板了,那麼你也可以在方法上加入泛型,具體轉換為什麼型別,通過呼叫者傳入泛型來轉換。

經過這樣一番改造之後,剛才我們遇到的兩個問題也都統統不是問題了,我們想要新增一個策略實現類,只需要實現定義的策略類即可,無需增加額外的任何程式碼。

更多內容可關注微信公眾號:一個程式設計師的成長

相關文章