手把手教你如何實現代碼擴展點設計

在我們寫業(yè)務代碼的時候,無可避免肯定會涉及到業(yè)務邏輯分支,從而寫if-else類似的語句。如果當前邏輯只有一個if-else,則不會過度影響代碼可讀性,但是當if 代碼塊的邏輯膨脹、else代碼塊的邏輯膨脹,那么整體代碼的可讀性就非常差,因為隨著邏輯膨脹,if-else 代碼塊還會繼續(xù)出現更多的if-else。
正常情況下,初學者一般都會封裝方法,即把if代碼塊的邏輯封裝到另外一個方法里面,這樣子看起來恢復了之前的樣子,只有一個if-else。但是,這樣其實并沒有解決根本問題:代碼可讀性差。 因為在閱讀代碼的時候,還是會進入封裝的方法內,然后方法內會有更多的if-else等著你。隨著邏輯分支的增多,人的腦力一般是否無法同時記住之前的邏輯,所以整體代碼的可讀性還是很差。
那遇到這類問題,我們如何解決呢?
根本原因是我們腦力無法去深入梳理邏輯,歸納邏輯,總是看后面忘了前面,那么如果我們把多個層級的邏輯梳理出來,歸納成單層級邏輯,那么整體的代碼可讀性就容易理解了。原來的多層級邏輯實例如下圖:

類似這種,就是多層級邏輯結構,如果只是封裝方法,那么作為代碼閱讀者就會陷入各種if else 內部的方法跳轉中,而無法從整體去理解業(yè)務。
那么基于這種常見的多層級邏輯,我們應該拆分為單層級邏輯,如下圖:

這樣子,我們在代碼處理上,就可以拆分為1個業(yè)務邏輯處理接口、4個業(yè)務邏輯處理實現類,1個工廠類或者上下文類。 然后在主邏輯代碼塊上通過工廠類獲取封業(yè)務邏輯處理接口實例,然后調用處理方法。從而實現業(yè)務的封裝和抽象,即把這塊相關邏輯抽象為一個接口,不同的實現。下次如果再增加一個邏輯,則只要新增一個實現類,在工廠方法新增一個類型標識即可,主邏輯代碼不變。
實現方案一
這里以路邊的地磁停車位為例,具體的線下業(yè)務是在停車場會安裝一個地磁硬件,當有車進來的時候會上報一個車輛駛入
事件,車出去的時候會上報一個車輛駛出
事件,除了這2事件,當車位被占用或者空閑的時候,會上報2個心跳:持續(xù)占用
和持續(xù)空閑
結合上面的業(yè)務,不同事件會有不同的處理方式,這里我們會創(chuàng)建一個狀態(tài)處理接口,該接口有2個方法,一個返回狀態(tài)枚舉、一個處理方法,如下:
public interface StateHandler {
? ?DeviceStatusEnum state();
? ?void handle(String deviceNo, String code);
}
然后,我們再創(chuàng)建一個工廠類或者叫上下文處理類,通過Spring的構造器注入所有實現StateHandler
接口的實現類,然后放入當前實例的緩存map中,另外還提供一個根據枚舉返回處理器的方法,具體如下:
@Component
public class StateContext {
? ?public Map<DeviceStatusEnum, StateHandler> stateMap = new HashMap<>();
? ?public StateContext(List<StateHandler> stateHandlerList){
? ? ? ?stateHandlerList.forEach(handler -> {
? ? ? ? ? ?stateMap.put(handler.state(), handler);
? ? ? ?});
? ?}
? ?public StateHandler getHandler(DeviceStatusEnum state){
? ? ? ?return stateMap.get(state);
? ?}
}
接下來就是具體狀態(tài)的處理實現類,這里只放車輛駛出
的處理類,如下:
@Component
public class OutStateHandler implements StateHandler{
? ?@Override
? ?public DeviceStatusEnum state() {
? ? ? ?return DeviceStatusEnum.OUT;
? ?}
? ?@Override
? ?public void handle(String deviceNo, String code) {
? ? ? ?// TODO
? ?}
}
這樣子,在主邏輯代碼只要根據狀態(tài)類型,從工廠類獲取處理器類,然后執(zhí)行方法即可,如下:
DeviceStatusEnum state = DeviceStatusEnum.get(status.getStatus());
StateHandler handler = stateContext.getHandler(state);
handler.handle(request.getSN(), request.getBerthCode());
這樣子即可消除對應的if-else
問題
通過上述的方案一,我們實現了消除基本的if-else,但是我們再深入思考下,方案一會有什么問題? 很明顯,這是針對具體業(yè)務的一個實現方式,該實現方式需要1個上下文類、1個接口、對應的N個實現類,那么當我們的業(yè)務代碼有多個不同業(yè)務的if-else,每個業(yè)務都需要創(chuàng)建2+N個類,造成了類膨脹。
所以,針對該問題,我們還需要對上述的實現方式進行抽象,讓它更實用 。
實現方案二(最終解決方案)
通過對方案一實現的思考,我們可以對以下幾個點進行優(yōu)化
針對接口類,我們可以抽象不同業(yè)務的接口,該接口只有一個方法,用于返回具體某個業(yè)務的枚舉類
針對枚舉類,不同業(yè)務會有不同的枚舉類,而枚舉類的抽象就是他們的父類:
Enum
針對上下文類,原來的map緩存key是具體某個枚舉類,value是不同的實現類;而這里為了實現多個業(yè)務擴展點,我們key可以設計為枚舉類的父類
Enum
,value為不同實現類的列表
具體的代碼如下:
創(chuàng)建一個抽象接口,用于返回具體某個業(yè)務的枚舉類
public interface IExtensionHandler<Y extends Enum>{
? ?Y extension();
}
這里的泛型Y就是具體某個業(yè)務的枚舉類
創(chuàng)建一個上下文類的接口,并實現其默認實現
public interface IExtensionHandlerFactory {
? ?/**
? ? * 添加擴展處理器
? ? * @param extensionHandler 處理器
? ? * @param <Y> 擴展點
? ? */
? ?<Y extends Enum<Y>>void addExtensionHandler(IExtensionHandler<Y> extensionHandler);
? ?/**
? ? * 獲取擴展點處理器
? ? * @param extension 擴展點
? ? * @param type 處理器類型
? ? * @param <T> 擴展處理器
? ? * @param <Y> 擴展點
? ? */
? ?<T extends IExtensionHandler<Y>,Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type);
}
上下文類的默認實現
@Slf4j
public class DefaultExtensionHandlerFactoryImpl implements IExtensionHandlerFactory, ApplicationContextAware {
? ?private final Map<Enum, List<IExtensionHandler>> serviceListCache = new ConcurrentHashMap<>();
? ?private final Map<ExtensionCacheKey, IExtensionHandler> serviceCache = new ConcurrentHashMap<>();
? ?@Override
? ?public <Y extends Enum<Y>> void addExtensionHandler(IExtensionHandler<Y> extensionHandler) {
? ? ? ?Assert.notNull(extensionHandler.extension(), "add extension handler failed, bean class " + extensionHandler.getClass().getName() + " extension is null");
? ? ? ?serviceListCache.putIfAbsent(extensionHandler.extension(), new LinkedList<>());
? ? ? ?serviceListCache.get(extensionHandler.extension()).add(extensionHandler);
? ?}
? ?@Override
? ?public <T extends IExtensionHandler<Y>, Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type) {
? ? ? ?ExtensionCacheKey<Y> cacheKey = new ExtensionCacheKey(extension, type);
? ? ? ?IExtensionHandler result = this.serviceCache.get(cacheKey);
? ? ? ?if (result == null) {
? ? ? ? ? ?List<IExtensionHandler> extensionHandlers = serviceListCache.getOrDefault(extension, Collections.synchronizedList(Collections.emptyList()));
? ? ? ? ? ?for (IExtensionHandler extensionHandler : extensionHandlers) {
? ? ? ? ? ? ? ?if (type.isAssignableFrom(extensionHandler.getClass())) {
? ? ? ? ? ? ? ? ? ?result = extensionHandler;
? ? ? ? ? ? ? ? ? ?serviceCache.put(cacheKey, result);
? ? ? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ? ? ?if (result == null) {
? ? ? ? ? ? ? ?log.warn("No IExtensionHandler found by CacheKey : " + cacheKey + " !");
? ? ? ? ? ?}
? ? ? ?}
? ? ? ?return (T) result;
? ?}
? ?@Override
? ?public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
? ? ? ?Map<String,IExtensionHandler> handlerMap = applicationContext.getBeansOfType(IExtensionHandler.class);
? ? ? ?log.info("瘋狂加載擴展ing ...");
? ? ? ?long start = System.currentTimeMillis();
? ? ? ?handlerMap.forEach((k, v) -> {
? ? ? ? ? ?//排除組合類自己
? ? ? ? ? ?if (!this.getClass().isAssignableFrom(v.getClass())) {
? ? ? ? ? ? ? ?addExtensionHandler(v);
? ? ? ? ? ?}
? ? ? ?});
? ? ? ?long end = System.currentTimeMillis();
? ? ? ?log.info("加載擴展點結束,耗時: {}, 擴展點個數: {}", end - start, handlerMap.size());
? ?}
}
在默認的上下文實現類中,我們還多了一個Map,該Map是為了提高擴展點的查找而設計的,如果只是使用serviceListCache
,那么每次根據某個業(yè)務的枚舉類會返回對應的擴展處理器列表,需要再循環(huán)一次才能找到對應的處理器,這里是設計了一個緩存Key: ExtensionCacheKey
, 代碼實現如下
@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class ExtensionCacheKey<T extends Enum> {
? ?private T extension;
? ?private Class<? extends IExtensionHandler<T>> extensionHandlerClass;
}
一個業(yè)務枚舉類,可能會有不同類型的擴展處理接口,serviceListCache的value值為什么設計成List和為什么需要設計一個ExtensionCacheKey的原因所在。
最后
這個擴展點設計雖然簡單,但是在我實踐的項目中有大量的使用,也推薦大家理解并使用,對于復雜業(yè)務的處理非常有幫助。
另外擴展點設計還有另外一種方式,基于注解的方式,具體實現可以搜下 COLA 4.0
獲取完整源碼地址:https://gitee.com/anyin/shiro-to-token