1. 程式人生 > 實用技巧 >Chain of Responsibility

Chain of Responsibility

設計模式目錄

責任鏈模式是一種行為設計模式, 允許你將請求沿著處理者鏈進行傳送。 收到請求後, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者。

責任鏈模式在核心 Java 程式庫中的一些示例:

識別方法: 該模式可通過一組物件的行為方法間接呼叫其他物件的相同方法來識別, 而且所有物件都會遵循相同的介面。

責任鏈模式結構

樣例

模擬新系統上限審批流程場景

而這審批的過程在隨著特定時間點會增加不同級別的負責人加入,每個人就像責任鏈模式中的每一個核心點。對於研發小夥伴並不需要關心具體的審批流程處理細節,只需要知道這個上線更嚴格,級別也更高,但對於研發人員來說同樣是點選相同的提審按鈕,等待稽核。

模擬稽核服務

package behavioral.chainofresponsibility;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 模擬稽核服務
 */
public class AuthService {

    private static Map<String, Date> authMap = new ConcurrentHashMap<>();

    /**
     * 查詢稽核結果
     * @param uId
     * @param orderId
     * @return
     */
    public static Date queryAuthInfo(String uId, String orderId) {
        return authMap.get(uId.concat(orderId));
    }

    /**
     * 處理稽核
     * @param uId
     * @param orderId
     */
    public static void auth(String uId, String orderId) {
        authMap.put(uId.concat(orderId), new Date());
    }
}

責任鏈中返回物件定義

package behavioral.chainofresponsibility;

public class AuthInfo {

    private String code;
    private String info = "";

    public AuthInfo(String code, String... infos) {
        this.code = code;
        for (String str : infos) {
            this.info = this.info.concat(str);
        }
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

鏈路抽象類定義

package behavioral.chainofresponsibility;

import behavioral.chainofresponsibility.AuthInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Date;

public abstract class AuthLink {
    protected Logger logger = LoggerFactory.getLogger(AuthLink.class);

    protected SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    protected String levelUserId;       //級別人員ID
    protected String levelUserName;     //級別人員名字
    private AuthLink next;              //責任鏈

    public AuthLink(String levelUserId, String levelUserName) {
        this.levelUserId = levelUserId;
        this.levelUserName = levelUserName;
    }

    public AuthLink next() {
        return next;
    }

    public AuthLink appendNext(AuthLink next) {
        this.next = next;
        return this;
    }

    public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}

AuthLink next 重點在於可以通過 next方式獲取下一個鏈路需要處理的節點

三個稽核實現類

package behavioral.chainofresponsibility;

import behavioral.chainofresponsibility.AuthInfo;
import behavioral.chainofresponsibility.AuthService;

import java.util.Date;

public class Level1AuthLink extends AuthLink {

    public Level1AuthLink(String levelUserId, String levelUserName) {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號: ", orderId, " 狀態: 待一級負責人審批, 審批人: ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號: ", orderId, " 狀態:一級審批完成", " 時間: ", format.format(date), " 審批人: ", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }
}




package behavioral.chainofresponsibility;

import behavioral.chainofresponsibility.AuthInfo;
import behavioral.chainofresponsibility.AuthService;

import java.text.ParseException;
import java.util.Date;

public class Level2AuthLink extends AuthLink {

    private Date beginDate = format.parse("2021-01-20 00:00:00");
    private Date endDate = format.parse("2021-02-20 00:00:00");

    public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號: ", orderId, " 狀態: 待二級負責人審批, 審批人: ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號: ", orderId, " 狀態:二級審批完成", " 時間: ", format.format(date), " 審批人: ", levelUserName);
        }

        if(authDate.before(beginDate) || authDate.after(endDate)){
            return new AuthInfo("0000", "單號: ", orderId, " 狀態:二級審批完成", " 時間: ", format.format(date), " 審批人: ", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }
}



package behavioral.chainofresponsibility;

import behavioral.chainofresponsibility.AuthInfo;
import behavioral.chainofresponsibility.AuthService;

import java.text.ParseException;
import java.util.Date;

public class Level3AuthLink extends AuthLink {

    private Date beginDate = format.parse("2021-01-20 00:00:00");
    private Date endDate = format.parse("2021-02-20 00:00:00");

    public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
        super(levelUserId, levelUserName);
    }

    @Override
    public AuthInfo doAuth(String uId, String orderId, Date authDate) {
        Date date = AuthService.queryAuthInfo(levelUserId, orderId);
        if (null == date) {
            return new AuthInfo("0001", "單號: ", orderId, " 狀態: 待三級負責人審批, 審批人: ", levelUserName);
        }
        AuthLink next = super.next();
        if (null == next) {
            return new AuthInfo("0000", "單號: ", orderId, " 狀態:三級審批完成", " 時間: ", format.format(date), " 審批人: ", levelUserName);
        }

        if(authDate.before(beginDate) || authDate.after(endDate)){
            return new AuthInfo("0000", "單號: ", orderId, " 狀態:三級審批完成", " 時間: ", format.format(date), " 審批人: ", levelUserName);
        }

        return next.doAuth(uId, orderId, authDate);
    }
}

測試

@Test
public void testAuthLink() throws ParseException {
    AuthLink authLink = new Level3AuthLink("1000013", "王工")
            .appendNext(new Level2AuthLink("1000012", "張經理")
                    .appendNext(new Level1AuthLink("1000011", "皮總")));

    //模擬三級負責人審批
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("花染夢", "100059878511", new Date())));
    logger.info("測試結果:{}", "模擬三級負責人審批,王工");
    AuthService.auth("1000013", "100059878511");

    //模擬二級負責人審批
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("花染夢", "100059878511", new Date())));
    logger.info("測試結果:{}", "模擬二級負責人審批,張經理");
    AuthService.auth("1000012", "100059878511");

    //模擬一級負責人審批
    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("花染夢", "100059878511", new Date())));
    logger.info("測試結果:{}", "模擬一級負責人審批,皮總");
    AuthService.auth("1000011", "100059878511");

    logger.info("測試結果:{}", JSON.toJSONString(authLink.doAuth("花染夢", "100059878511", new Date())));
}


/**
 *18:15:33.528 [main] INFO  behavioral.TestChainOfRe - 測試結果:{"code":"0001","info":"單號: 100059878511 狀態: 待三級負責人審批, 審批人: 王工"}
 * 18:15:33.548 [main] INFO  behavioral.TestChainOfRe - 測試結果:模擬三級負責人審批,王工
 * 18:15:33.548 [main] INFO  behavioral.TestChainOfRe - 測試結果:{"code":"0001","info":"單號: 100059878511 狀態: 待二級負責人審批, 審批人: 張經理"}
 * 18:15:33.548 [main] INFO  behavioral.TestChainOfRe - 測試結果:模擬二級負責人審批,張經理
 * 18:15:33.549 [main] INFO  behavioral.TestChainOfRe - 測試結果:{"code":"0001","info":"單號: 100059878511 狀態: 待一級負責人審批, 審批人: 皮總"}
 * 18:15:33.549 [main] INFO  behavioral.TestChainOfRe - 測試結果:模擬一級負責人審批,皮總
 * 18:15:33.549 [main] INFO  behavioral.TestChainOfRe - 測試結果:{"code":"0000","info":"單號: 100059878511 狀態:一級審批完成 時間: 2021-01-20 18:15:33 審批人: 皮總"}
 */

適用場景

  • 當程式需要使用不同方式處理不同種類請求, 而且請求型別和順序預先未知時, 可以使用責任鏈模式。

    該模式能將多個處理者連線成一條鏈。 接收到請求後, 它會 “詢問” 每個處理者是否能夠對其進行處理。 這樣所有處理者都有機會來處理請求。

  • 當必須按順序執行多個處理者時, 可以使用該模式。

    無論你以何種順序將處理者連線成一條鏈, 所有請求都會嚴格按照順序通過鏈上的處理者。

  • 如果所需處理者及其順序必須在執行時進行改變, 可以使用責任鏈模式。

    如果在處理者類中有對引用成員變數的設定方法, 你將能動態地插入和移除處理者, 或者改變其順序。

實現方式

  1. 宣告處理者介面並描述請求處理方法的簽名。

    確定客戶端如何將請求資料傳遞給方法。 最靈活的方式是將請求轉換為物件, 然後將其以引數的形式傳遞給處理函式。

  2. 為了在具體處理者中消除重複的樣本程式碼, 你可以根據處理者介面建立抽象處理者基類。

    該類需要有一個成員變數來儲存指向鏈上下個處理者的引用。 你可以將其設定為不可變類。 但如果你打算在執行時對鏈進行改變, 則需要定義一個設定方法來修改引用成員變數的值。

    為了使用方便, 你還可以實現處理方法的預設行為。 如果還有剩餘物件, 該方法會將請求傳遞給下個物件。 具體處理者還能夠通過呼叫父物件的方法來使用這一行為。

  3. 依次建立具體處理者子類並實現其處理方法。 每個處理者在接收到請求後都必須做出兩個決定:

    • 是否自行處理這個請求。
    • 是否將該請求沿著鏈進行傳遞。
  4. 客戶端可以自行組裝鏈, 或者從其他物件處獲得預先組裝好的鏈。 在後一種情況下, 你必須實現工廠類以根據配置或環境設定來建立鏈。

  5. 客戶端可以觸發鏈中的任意處理者, 而不僅僅是第一個。 請求將通過鏈進行傳遞, 直至某個處理者拒絕繼續傳遞, 或者請求到達鏈尾。

  6. 由於鏈的動態性, 客戶端需要準備好處理以下情況:

    • 鏈中可能只有單個連結。
    • 部分請求可能無法到達鏈尾。
    • 其他請求可能直到鏈尾都未被處理。

責任鏈模式優點

  • 你可以控制請求處理的順序。
  • 單一職責原則。 你可對發起操作和執行操作的類進行解耦。
  • 開閉原則。 你可以在不更改現有程式碼的情況下在程式中新增處理者。

責任鏈模式缺點

  • 部分請求可能未被處理。