最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

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

2023-02-08 18:24 作者:Anyin灬  | 我要投稿


引言

在我們寫業(yè)務代碼的時候,無可避免肯定會涉及到業(yè)務邏輯分支,從而寫if-else類似的語句。如果當前邏輯只有一個if-else,則不會過度影響代碼可讀性,但是當if 代碼塊的邏輯膨脹、else代碼塊的邏輯膨脹,那么整體代碼的可讀性就非常差,因為隨著邏輯膨脹,if-else 代碼塊還會繼續(xù)出現更多的if-else。

正常情況下,初學者一般都會封裝方法,即把if代碼塊的邏輯封裝到另外一個方法里面,這樣子看起來恢復了之前的樣子,只有一個if-else。但是,這樣其實并沒有解決根本問題:代碼可讀性差。 因為在閱讀代碼的時候,還是會進入封裝的方法內,然后方法內會有更多的if-else等著你。隨著邏輯分支的增多,人的腦力一般是否無法同時記住之前的邏輯,所以整體代碼的可讀性還是很差。

那遇到這類問題,我們如何解決呢?

思路

根本原因是我們腦力無法去深入梳理邏輯,歸納邏輯,總是看后面忘了前面,那么如果我們把多個層級的邏輯梳理出來,歸納成單層級邏輯,那么整體的代碼可讀性就容易理解了。原來的多層級邏輯實例如下圖:

tapd_30605327_1624761756_64.png

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

那么基于這種常見的多層級邏輯,我們應該拆分為單層級邏輯,如下圖:

tapd_30605327_1624761968_82.png

這樣子,我們在代碼處理上,就可以拆分為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為不同實現類的列表

具體的代碼如下:

  1. 創(chuàng)建一個抽象接口,用于返回具體某個業(yè)務的枚舉類

public interface IExtensionHandler<Y extends Enum>{
? ?Y extension();
}

這里的泛型Y就是具體某個業(yè)務的枚舉類

  1. 創(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


手把手教你如何實現代碼擴展點設計的評論 (共 條)

分享到微博請遵守國家法律
枣阳市| 凌云县| 宝坻区| 永丰县| 达尔| 富裕县| 通州市| 婺源县| 莱州市| 白城市| 平定县| SHOW| 商南县| 林口县| 额济纳旗| 沁水县| 和政县| 石柱| 香河县| 盐津县| 元谋县| 河东区| 萨迦县| 玉溪市| 资中县| 宣威市| 清水县| 姚安县| 巧家县| 尼勒克县| 贵南县| 独山县| 连州市| 寿光市| 青海省| 北碚区| 弋阳县| 黄陵县| 凤山县| 阜阳市| 来宾市|