責任鏈模式的實踐

xuanhaoo發表於2021-05-26

責任鏈模式

基本概念

 責任鏈(Chain of Responsibility)模式的定義:為了避免請求傳送者與多個請求處理者耦合在一起,於是將所有請求的處理者通過前一物件記住其下一個物件的引用而連成一條鏈;當有請求發生時,可將請求沿著這條鏈傳遞,直到有物件處理它為止。

關於責任鏈的詳細介紹可以點選這裡進行了解。就不在這裡過多敘述啦。

 要來就來點實際的乾貨:在做公司專案(會員積分商城)專案時,會員模組有個會員降級任務,在開發這個功能的時候,我就將責任鏈模式集合業務融入了進去。

 結合業務模式,降級策略配置有三個條件,當這三個條件有一滿足則不執行降級,所以如果用if/else來操作那麼應該會寫很多,且邏輯不清晰,不夠優雅。適當的運用設計模式,使邏輯清晰明瞭,也方便後續同學閱讀。

  業務的整體流程圖如下:

  抽象的職責節點的UML圖如下:

  上程式碼實戰看看:

部分程式碼使用Test表示啦,大家都懂的哦

  • 基礎介面:BaseChainHandle.java
public interface BaseChainHandle {

    /**
     * 責任type
     * @return
     */
    Integer getChainType();
}

  • 抽象父類:AbstractRelegationChainHandle.java
public abstract class AbstractRelegationChainHandle implements BaseChainHandle {


    /**
     * 下一個節點
     */
    private AbstractRelegationChainHandle nextChain;

    /**
     * 對外暴露方法
     * @return
     */
    public abstract boolean execute(RelegationChainDTO dto);


    public AbstractRelegationChainHandle getNextChain() {
        return nextChain;
    }

    public AbstractRelegationChainHandle setNextChain(AbstractRelegationChainHandle nextChain) {
        this.nextChain = nextChain;
        return this.nextChain;
    }

    public AbstractRelegationChainHandle getCurChain() {
        return this;
    }
}

  • 空節點子類:

設定該節點的意義主要是為了構造連結串列時的初始化首節點。

@Component
public class NotChainHandle extends AbstractRelegationChainHandle{


    /**
     * 設定空節點的意義是:該節點作為起始節點,不做任何操作,僅構造責任例項聯時使用
     * @return
     */


    @Override
    public Integer getChainType() {
        return -1;
    }

    @Override
    public boolean execute(RelegationChainDTO dto) {
        if (Objects.nonNull(this.getNextChain())) {
            // 向下傳遞
            return this.getNextChain().execute(dto);
        } else {
            return Boolean.FALSE;
        }
    }
}
  • 具體邏輯節點子類:(其他責任節點類似,不再列舉)
package com.test;

@Slf4j
@Component
public class Test1Handle extends AbstractRelegationChainHandle {



    @Override
    public Integer getChainType() {
        return RelegationStrategyEnum.Test1.getCode();
    }

    @SneakyThrows
    @Override
    public boolean execute(RelegationChainDTO dto) {
        // 業務邏輯處理...
        if (Objects.nonNull(mkRecordTO)) {
            
            if (//condition) {
                return Boolean.TRUE;
            }
        }
        
        if (Objects.nonNull(this.getNextChain())) {
            // 向下傳遞
            return this.getNextChain().execute(dto);
        } else {
            return Boolean.FALSE;
        }
    }
}

  • 在工廠中將節點bean例項化,類似於策略模式使用@Autowire自動注入
package com.test;
 
 
/**
 * @description 會員保級責任鏈 裝配節點工廠
 */
 
@Component
public class RelegationChainHandleFactory {
 
 
    /**
     * 自動注入節點進來:必須是String,因為自動注入會將beanName設定為String
     */
 
    @Autowired
    private Map<String, AbstractRelegationChainHandle> relegationChainMap;
 
 
    private final Map<Integer, AbstractRelegationChainHandle> relegationChainMapContext = new HashMap<>();
 
 
    @PostConstruct
    public void setRelegationChainMap() {
        for (AbstractRelegationChainHandle obj : relegationChainMap.values()) {
            relegationChainMapContext.put(obj.getChainType(), obj);
        }
    }
 
    public AbstractRelegationChainHandle getChainHandleContext(Integer type) {
        return relegationChainMapContext.get(type);
    }
}
  • 在業務邏輯中構造責任鏈:並執行節點邏輯

請看下方程式碼:為什麼要初始化一個ThreadLocal變數來儲存責任鏈:初始化一個ThreadLocal變數來儲存該次動態責任鏈十分重要,具體原因可見程式碼中的相關注釋內容。

package com.test;
......
    /**
     * 每一個子執行緒儲存降級的動態責任鏈
     */
    private ThreadLocal<AbstractRelegationChainHandle> startChain = new ThreadLocal<>();
    
 .......


 
    private void downLevelTest(Dto dto) {
     		// ... 業務邏輯處理
            // 2. 匹配保級規則
            // 2.1 構建保級規則責任鏈
            /**
             * 使用ThreadLocal儲存該子執行緒的責任鏈:因為Spring例項化bean預設單例的,多執行緒環境下,bean的例項會被多個執行緒獲取,
             * 造成安全問題,所以使用ThreadLocal為每一個子執行緒分配一個空間,儲存每一個子執行緒的動態責任鏈,隔離執行緒
             */
            // 裝載責任鏈節點
            // 初始化責任鏈開始節點 type: -1
            startChain.set(relegationChainHandleFactory.getChainHandleContext(-1));
            // 手動置空:因為當其他執行緒例項化的該責任節點類的next可能會已經存在資料,我們要動態拼裝責任鏈(下同)
            startChain.get().setNextChain(null);
            for (int i = 0; i < relegationStrategy.size(); i++) {
                // 構造責任鏈:類似於構造連結串列的方式
                // TODO 通過switch case不太優雅,目前暫時想到使用該方法進行構造責任鏈,待優化
                // 嘗試過其他寫法來拼裝(使用臨時變數(多個ThreadLocal)等)
                switch (i) {
                    case 0:
                        startChain.get().setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
                        startChain.get().getNextChain().setNextChain(null);
                    break;
                    case 1:
                        startChain.get().getNextChain()
                            .setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
                        startChain.get().getNextChain().getNextChain().setNextChain(null);
                    break;
                    case 2:
                        startChain.get().getNextChain().getNextChain()
                                .setNextChain(relegationChainHandleFactory.getChainHandleContext(relegationStrategy.get(i)));
                        startChain.get().getNextChain().getNextChain().getNextChain().setNextChain(null);
                    break;
                    default:
                }
            }
            // ... 業務處理
               
        } catch (Exception e) {
            log.info("處理任務發生異常,account:{},e:{}", account, e);

        } finally {
            startChain.remove();
        }
    }

有些寫法雖然也不太優雅,請見諒哈,鄙人水平有限,還需要多加學習!歡迎大家指出來一起探討探討

總結

 設計模式雖然讓程式碼看起來變多了很多,但是這樣寫可以使我們的邏輯清晰,並且方便擴充套件,提高程式碼的健壯性。
但有個問題需要提醒大家,在平時的開發過程中不能為了設計而設計,這樣就會造成過度設計,反而起反作用,一定要結合自身業務來考慮,技術也是為了實現業務而服務的。

相關文章