設計模式之不一樣的責任鏈模式

2023-06-05 06:00:41

責任鏈模式(Chain of Responsibility Pattern)是一種行為型設計模式,它通過將請求的傳送者和接收者解耦,使多個物件都有機會處理請求。在這個模式中,請求沿著一個處理鏈依次傳遞,直到有一個物件能夠處理它為止。

本文將詳細介紹責任鏈模式的概述、應用場景以及程式碼範例,來幫助讀者更好地理解和應用這個模式。

1. 簡介

模式概述

責任鏈模式的核心思想是將請求的傳送者和接收者解耦,使得多個物件都有機會處理請求。在責任鏈模式中,請求會沿著一個處理鏈依次傳遞,每個處理者都有機會處理請求,如果一個處理者不能處理請求,則將請求傳遞給下一個處理者,直到有一個處理者能夠處理它。

責任鏈模式包含以下幾個角色:

  • 抽象處理者(Handler):定義了處理請求的介面,通常包含一個指向下一個處理者的參照,用於將請求傳遞給下一個處理者。
  • 具體處理者(ConcreteHandler):實現了處理請求的介面,具體處理者可以決定是否處理請求,如果不能處理,則將請求傳遞給下一個處理者。
  • 使用者端(Client):建立處理者物件並組成責任鏈的結構,負責將請求傳送給第一個處理者。

優點與缺點

優點:

  • 責任鏈模式可以實現請求的傳送者和接收者之間的解耦。傳送者只需要將請求傳送給第一個處理者,無需關心具體是哪個處理者來處理。這樣,系統的靈活性大大增強,可以隨時增加或修改處理者的順序。
  • 責任鏈模式能夠避免請求的傳送者和接收者之間的緊耦合。每個處理者只需要關心自己負責的請求型別,無需關心其他請求。這樣,系統的可維護性也得到了提升。
  • 責任鏈模式可以靈活地動態新增或刪除處理者。我們可以根據實際情況來調整責任鏈的結構,以滿足不同的業務需求。

缺點:

  • 複雜度會明顯提升,如果責任鏈過長或者處理者之間的關係複雜,可能還會導致效能下降和偵錯困難。

應用場景

責任鏈模式在許多不同的應用場景中都有廣泛的應用。下面列舉了一些常見的應用場景:

  • 請求處理鏈:當一個請求需要經過多個處理步驟或處理者進行處理時,可以使用責任鏈模式。每個處理者負責一部分邏輯,處理完後可以選擇將請求傳遞給下一個處理者,從而形成一個處理鏈。
  • 紀錄檔記錄:在紀錄檔系統中,可以使用責任鏈模式來記錄紀錄檔。不同的處理者可以負責不同級別的紀錄檔記錄,例如,一個處理者負責記錄錯誤紀錄檔,另一個處理者負責記錄偵錯紀錄檔,然後按照鏈式結構傳遞紀錄檔。
  • 身份驗證和許可權檢查:在身份驗證和許可權檢查系統中,可以使用責任鏈模式來驗證使用者的身份和許可權。每個處理者可以檢查特定的條件,例如使用者名稱和密碼的正確性、賬戶是否鎖定等。如果一個處理者無法通過驗證,可以將請求傳遞給下一個處理者。
  • 資料過濾和轉換:在資料處理過程中,可以使用責任鏈模式來進行資料過濾和轉換。每個處理者可以根據特定的條件過濾資料或對資料進行轉換,然後將處理後的資料傳遞給下一個處理者。
  • 錯誤處理和例外處理:在錯誤處理和例外處理系統中,可以使用責任鏈模式來處理錯誤和異常。不同的處理者可以處理不同型別的錯誤或異常,並根據需要將錯誤或異常傳遞給下一個處理者進行進一步處理或記錄。

2. Java 程式碼範例

Java 中實現責任鏈模式有多種方式,包括基於介面、基於抽象類、基於註解等。下面將詳細介紹基於介面的常見實現方式。

基於介面的實現方式是通過定義一個處理請求的介面,每個處理者實現這個介面,並在自己的實現中決定是否處理請求和傳遞請求給下一個處理者。

首先,我們定義一個處理請求的介面 Handler 以及請求入參 Request

public interface Handler {
    void handleRequest(Request request);
}

public class Request {
    private String type;
    // 省略getter、setter
}

然後,我們建立3個具體的處理者類實現這個介面,在具體處理者類的實現中,首先判斷自己是否能夠處理請求,如果能夠處理,則進行處理;否則將請求傳遞給下一個處理者。程式碼如下:

public class ConcreteHandlerA implements Handler {
    private Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public void handleRequest(Request request) {
        if (request.getType().equals("A")) {
            // 處理請求的邏輯
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

public class ConcreteHandlerB implements Handler {
    private Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public void handleRequest(Request request) {
        if (request.getType().equals("B")) {
            // 處理請求的邏輯
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

public class ConcreteHandlerC implements Handler {
    private Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public void handleRequest(Request request) {
        if (request.getType().equals("C")) {
            // 處理請求的邏輯
        } else if (successor != null) {
            successor.handleRequest(request);
        }
    }
}

接下來,我們建立一個使用者端類 Client,用於建立處理者物件並組成責任鏈的結構:

public class Client {
    public static void main(String[] args) {
        Handler handlerA = new ConcreteHandlerA();
        Handler handlerB = new ConcreteHandlerB();
        Handler handlerC = new ConcreteHandlerC();

        handlerA.setSuccessor(handlerB);
        handlerB.setSuccessor(handlerC);

        // 建立請求並行送給第一個處理者
        Request request = new Request("A");
        handlerA.handleRequest(request);
    }
}

在使用者端類中,我們建立了具體的處理者物件,並通過 setSuccessor() 方法將它們組成一個責任鏈的結構。然後,建立一個請求物件,並將請求傳送給第一個處理者。

基於介面的實現方式簡單直觀,每個處理者只需要實現一個介面即可。但是它的缺點是如果責任鏈較長,需要建立多個處理者物件,增加了系統的複雜性和資源消耗。下面基於 Spring 框架實現一個高階版的責任鏈模式。

3. Spring 程式碼範例

在實際開發中,一個請求會在多個處理器之間流轉,每個處理器都可以處理請求。

假設我們有一個 Spring 框架開發的訂單處理系統,訂單需要依次經過訂單檢查、庫存處理、支付處理。如果某個處理環節無法處理訂單,將會終止處理並返回錯誤資訊,只有每個處理器都完成了請求處理,這個訂單才演演算法下單成功。

首先,我們定義一個訂單類 Order

@Data
@AllArgsConstructor
public class orderNo {
    private String orderNumber;
    private String paymentMethod;
    private boolean stockAvailability;
    private String shippingAddress;
}

然後,我們定義一個抽象訂單處理者類 OrderHandler

public abstract class OrderHandler {
    public abstract void handleOrder(Order order);
}

接下來,我們建立具體的訂單處理者類繼承自抽象訂單處理者類,實現相應的方法,並註冊到 Spring 中,

@Component
public class CheckOrderHandler extends OrderHandler {
    public void handleOrder(Order order) {
        if (StringUtils.isBlank(order.getOrderNo())) {
            throw new RuntimeException("訂單編號不能為空");
        }
        if (order.getPrice().compareTo(BigDecimal.ONE) <= 0) {
            throw new RuntimeException("訂單金額不能小於等於0");
        }
        if (StringUtils.isBlank(order.getShippingAddress())) {
            throw new RuntimeException("收貨地址不能為空");
        }
        System.out.println("訂單引數檢驗通過");
    }
}

@Component
public class StockHandler extends OrderHandler {
    public void handleOrder(Order order) {
        if (!order.isStockAvailability()) {
            throw new RuntimeException("訂單庫存不足");
        }
        System.out.println("庫存扣減成功");
    }
}

@Component
public class AliPaymentHandler extends OrderHandler {
    public void handleOrder(Order order) {
        if (!order.getPaymentMethod().equals("支付寶")) {
            throw new RuntimeException("不支援支付寶以外的支付方式");
        }
        System.out.println("支付寶預下單成功");
    }
}

在具體訂單處理者類的實現中,CheckOrderHandler 負責做訂單引數檢查、StockHandler 負責做庫存扣減、AliPaymentHandler 負責做預下單,每個處理者的邏輯都是相互獨立各不不干擾。


最後,我們建立一個訂單生產鏈條 BuildOrderChain ,用於組成責任鏈的鏈條處理結構:

@Component
public class BuildOrderChain {

    @Autowired
    private AliPaymentHandler aliPaymentHandler;

    @Autowired
    private CheckOrderHandler checkOrderHandler;

    @Autowired
    private StockHandler stockHandler;

    List<OrderHandler> list = new ArrayList<>();

    @PostConstruct
    public void init() {
        // 1. 檢查訂單引數
        list.add(checkOrderHandler);
        // 2. 扣減庫存
        list.add(stockHandler);
        // 3. 支付寶預下單
        list.add(aliPaymentHandler);
    }

    public void doFilter(Order order) {
        for (OrderHandler orderHandler : this.list) {
            orderHandler.handleOrder(order);
        }
    }
}

訂單生產鏈條 BuildOrderChain 類中,我們通過 @PostConstruct 註解下的 init() 初始化方法,將具體的訂單處理者按程式碼順序組成一個責任鏈的結構。然後通過 doFilter(order) 方法遍歷處理者集合依次處理。

執行程式碼:

@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class OrderChainTest {
    @Autowired
    private BuildOrderChain buildOrderChain;

    @Test
    public void test() {
        Order order = new Order("123456", "支付寶",
                      true, "長沙", new BigDecimal("100"));
        buildOrderChain.doFilter(order);
    }

}

-------------------------------
訂單引數檢驗通過
庫存扣減成功
支付寶預下單成功

可以看到訂單依次經過校驗處理器、庫存處理器和支付處理器進行處理,直到最後完成整個訂單的處理。


在舉個例子,假如我們的訂單針對的是虛擬不限庫存商品,我們不需要進行庫存扣減,那我們可以直接新建 VirtualGoodsOrderChain 虛擬商品訂單生產鏈條類,程式碼如下,

@Component
public class VirtualGoodsOrderChain {
    @Autowired
    private AliPaymentHandler aliPaymentHandler;

    @Autowired
    private CheckOrderHandler checkOrderHandler;

    List<OrderHandler> list = new ArrayList<>();

    @PostConstruct
    public void init() {
        // 1. 檢查訂單引數
        list.add(checkOrderHandler);
        // 2 支付寶預下單
        list.add(aliPaymentHandler);
    }

    public void doFilter(Order order) {
        for (OrderHandler orderHandler : this.list) {
            orderHandler.handleOrder(order);
        }
    }
}

執行程式碼:

@Test
public void virtualOrderTest() {
    Order order = new Order("123456", "支付寶", true, "長沙", new BigDecimal("100"));
    virtualGoodsOrderChain.doFilter(order);
}

-------------------------------------------
訂單引數檢驗通過
支付寶預下單成功

4. 總結

總的來說,責任鏈模式適用於存在多個處理步驟、每個處理步驟具有獨立邏輯或條件、需要靈活組合和擴充套件的場景。通過責任鏈模式,可以將複雜的處理邏輯拆分為多個獨立的處理步驟,並且可以動態地組合和調整處理步驟的順序,從而提高系統的靈活性和可維護性。希望本文能夠幫助讀者理解和應用責任鏈模式,提升軟體設計和開發的能力。

關注公眾號【waynblog】每週分享技術乾貨、開源專案、實戰經驗、高效開發工具等,您的關注將是我的更新動力!