策略模式結構圖
策略模式主要由以上三個身份組成,這裡我們就不過多及時策略模式的基礎知識,預設大家已經對策略模式已經有了一個基礎的認識。
業務需求
現有一個廣告點選資料埋點上報的需求,上報的埋點資料根據點選的廣告位置不同做區分進行上報,每個廣告位置的資料進行分表儲存。(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的方式,在方法內部進行轉換,當然了,如果這樣你嫌策略方法太死板了,那麼你也可以在方法上加入泛型,具體轉換為什麼型別,通過呼叫者傳入泛型來轉換。
經過這樣一番改造之後,剛才我們遇到的兩個問題也都統統不是問題了,我們想要新增一個策略實現類,只需要實現定義的策略類即可,無需增加額外的任何程式碼。