一文搞懂設(shè)計(jì)模式—責(zé)任鏈模式
責(zé)任鏈模式(Chain of Responsibility Pattern)是一種行為型設(shè)計(jì)模式,它允許多個(gè)對(duì)象按照順序處理請(qǐng)求,并且每個(gè)對(duì)象可以選擇自己是否處理該請(qǐng)求或?qū)⑵鋫鬟f給下一個(gè)對(duì)象。這種模式將請(qǐng)求的發(fā)送者和接收者解耦,同時(shí)提供了更大的靈活性和可擴(kuò)展性。
簡(jiǎn)介
責(zé)任鏈模式通過(guò)將多個(gè)處理請(qǐng)求的對(duì)象組成一條鏈,使請(qǐng)求在鏈上傳遞,直到有一個(gè)對(duì)象處理它為止。每個(gè)處理對(duì)象都負(fù)責(zé)判斷自己能否處理該請(qǐng)求,如果可以則進(jìn)行處理,否則將請(qǐng)求傳遞給下一個(gè)處理對(duì)象。這樣,請(qǐng)求發(fā)送者無(wú)需知道具體的處理對(duì)象,只需將請(qǐng)求發(fā)送到責(zé)任鏈上即可。
責(zé)任鏈模式包含以下角色:
- 抽象處理者(Handler):定義一個(gè)處理請(qǐng)求的接口,并持有下一個(gè)處理者的引用。
- 具體處理者(Concrete Handler):實(shí)現(xiàn)抽象處理者的接口,在處理請(qǐng)求前判斷自己是否能夠處理該請(qǐng)求,如果可以則進(jìn)行處理,否則將請(qǐng)求傳遞給下一個(gè)處理者。
通過(guò)責(zé)任鏈模式,我們可以動(dòng)態(tài)地組合處理對(duì)象,靈活地配置處理流程,這種解耦使得系統(tǒng)更加靈活和可擴(kuò)展。
使用場(chǎng)景
責(zé)任鏈模式常用于以下場(chǎng)景:
- 動(dòng)態(tài)組合處理流程:通過(guò)靈活配置責(zé)任鏈,可以動(dòng)態(tài)地組合處理對(duì)象,實(shí)現(xiàn)不同的處理流程。每個(gè)處理者只需關(guān)注自己負(fù)責(zé)處理的請(qǐng)求,使得系統(tǒng)更加靈活和可擴(kuò)展。
- 避免請(qǐng)求的發(fā)送者和接收者之間的直接耦合:通過(guò)將請(qǐng)求傳遞給責(zé)任鏈,請(qǐng)求發(fā)送者無(wú)需知道具體的處理對(duì)象,減少了對(duì)象之間的依賴關(guān)系。
- 處理請(qǐng)求的順序可變:責(zé)任鏈模式允許在運(yùn)行時(shí)根據(jù)需要改變處理請(qǐng)求的順序,靈活調(diào)整處理流程。
常見(jiàn)的實(shí)際應(yīng)用場(chǎng)景包括:
- 日志記錄器鏈:一個(gè)日志記錄系統(tǒng)可以根據(jù)日志級(jí)別將日志消息傳遞給不同的日志記錄器,如控制臺(tái)記錄器、文件記錄器、數(shù)據(jù)庫(kù)記錄器等。
- 審批流程:一個(gè)多級(jí)審批系統(tǒng)可以根據(jù)審批者的權(quán)限和級(jí)別將審批請(qǐng)求傳遞給下一個(gè)級(jí)別的審批者,直到獲得最終的審批結(jié)果。
- 異常處理:一個(gè)異常處理系統(tǒng)可以根據(jù)異常類型將異常進(jìn)行分類處理,如日志記錄、郵件通知、異常展示等。
責(zé)任鏈模式在這些場(chǎng)景中可以減少代碼的耦合性,提高代碼的可維護(hù)性和可擴(kuò)展性。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 解耦發(fā)送者和接收者:責(zé)任鏈模式將請(qǐng)求的發(fā)送者和接收者解耦,發(fā)送者無(wú)需知道具體的處理對(duì)象,只需將請(qǐng)求發(fā)送到責(zé)任鏈上即可。
- 靈活動(dòng)態(tài)的處理流程:通過(guò)配置責(zé)任鏈,可以靈活地組合處理對(duì)象,實(shí)現(xiàn)不同的處理流程,并且可以在運(yùn)行時(shí)動(dòng)態(tài)地改變處理的順序。
- 增強(qiáng)代碼的可擴(kuò)展性:由于責(zé)任鏈模式遵循開(kāi)閉原則,新的處理者可以隨時(shí)被加入到責(zé)任鏈中,不需要修改已有代碼,提供了良好的擴(kuò)展性。
- 增強(qiáng)代碼的可維護(hù)性:每個(gè)處理者只需關(guān)注自己負(fù)責(zé)處理的請(qǐng)求,職責(zé)單一,使得代碼更加清晰、可讀性更高。
缺點(diǎn):
- 請(qǐng)求的處理不保證被處理:由于責(zé)任鏈中的每個(gè)處理者都可以選擇是否處理請(qǐng)求,如果沒(méi)有正確配置責(zé)任鏈或者某個(gè)處理者沒(méi)有正確處理請(qǐng)求,可能會(huì)導(dǎo)致請(qǐng)求無(wú)法被處理。
- 性能問(wèn)題:當(dāng)責(zé)任鏈過(guò)長(zhǎng)或者請(qǐng)求在責(zé)任鏈中被頻繁傳遞時(shí),可能會(huì)對(duì)性能產(chǎn)生影響。因此,在設(shè)計(jì)責(zé)任鏈時(shí)需要注意鏈的長(zhǎng)度和處理的復(fù)雜度。
- 調(diào)試不方便:當(dāng)責(zé)任鏈特別是鏈條比較長(zhǎng), 環(huán)節(jié)比較多的時(shí)候,由于采用了類似遞歸的方式,調(diào)試的時(shí)候邏輯可能比較復(fù)雜。
在實(shí)際應(yīng)用中,我們需要根據(jù)具體情況評(píng)估責(zé)任鏈模式的優(yōu)缺點(diǎn),并合理地選擇使用或者改進(jìn)這個(gè)模式。
責(zé)任鏈模式實(shí)現(xiàn)
要實(shí)現(xiàn)責(zé)任鏈模式,我們按照以下步驟進(jìn)行:
- 定義處理者接口(Handler),聲明處理方法,并添加設(shè)置下一個(gè)處理者的方法。
- 實(shí)現(xiàn)具體處理者類(ConcreteHandler),實(shí)現(xiàn)處理方法,并在需要時(shí)選擇是否調(diào)用下一個(gè)處理者。
- 在客戶端代碼中創(chuàng)建責(zé)任鏈,并將請(qǐng)求發(fā)送到責(zé)任鏈上的第一個(gè)處理者。
以下是一個(gè)簡(jiǎn)單的示例,演示如何使用責(zé)任鏈模式處理請(qǐng)假申請(qǐng):
步驟一:定義處理者接口(Handler)
/**
* <p>
* 責(zé)任鏈模式——抽象類處理器
* </p>
*/
public abstract class AbstractHandler {
/**
* 責(zé)任鏈中的下一個(gè)元素
*/
protected AbstractHandler nextHandler;
public AbstractHandler setNextChain(AbstractHandler nextHandler) {
this.nextHandler = nextHandler;
return nextHandler;
}
/**
* 責(zé)任鏈處理邏輯
*/
public void linkChain(LeaveRequest request) {
handler(request);
//這里還可以加入其他方法
if (Objects.nonNull(nextHandler)) {
nextHandler.linkChain(request);
}
}
/**
* 抽象方法
*/
protected abstract void handler(LeaveRequest request);
}
請(qǐng)求對(duì)象為:
@Getter
public class LeaveRequest {
private final String employee;
private final int days;
public LeaveRequest(String employee, int days) {
this.employee = employee;
this.days = days;
}
}
步驟二:實(shí)現(xiàn)具體處理者類(ConcreteHandler)
@Slf4j
public class Handler1 extends AbstractHandler {
@Override
public void handler(LeaveRequest request) {
if (request.getDays() <= 3) {
log.info("ConcreteHandlerA 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
@Slf4j
public class Handler2 extends AbstractHandler {
@Override
public void handler(LeaveRequest request) {
if (request.getDays() > 3 && request.getDays() <= 7) {
log.info("ConcreteHandlerB 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
@Slf4j
public class Handler3 extends AbstractHandler {
@Override
protected void handler(LeaveRequest request) {
if (request.getDays() > 7) {
log.info("ConcreteHandlerC 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
步驟三:在客戶端代碼中創(chuàng)建責(zé)任鏈,并將請(qǐng)求發(fā)送到責(zé)任鏈上的第一個(gè)處理者
public class ChainPatternDemo {
private static AbstractHandler getChainOfHandler() {
AbstractHandler handler1 = new Handler1();
AbstractHandler handler2 = new Handler2();
AbstractHandler handler3 = new Handler3();
//可以自定義鏈路順序
handler1.setNextChain(handler2).setNextChain(handler3);
return handler1;
}
public static void main(String[] args) {
AbstractHandler chain = getChainOfHandler();
LeaveRequest request1 = new LeaveRequest("張三", 2);
chain.linkChain(request1);
LeaveRequest request2 = new LeaveRequest("李四", 5);
chain.linkChain(request2);
LeaveRequest request3 = new LeaveRequest("王五", 10);
chain.linkChain(request3);
}
}
在上述示例中,我們定義了三個(gè)具體處理者類:Handler1、Handler2和Handler3,它們分別處理請(qǐng)假申請(qǐng)??蛻舳舜a創(chuàng)建了責(zé)任鏈,并將請(qǐng)求發(fā)送給第一個(gè)處理者Handler1。每個(gè)具體處理者判斷自己是否能夠處理該請(qǐng)求,如果可以則進(jìn)行處理,否則傳遞給下一個(gè)處理者。
運(yùn)行以上代碼,輸出結(jié)果為:
ConcreteHandlerA 處理了 張三 的請(qǐng)假申請(qǐng),天數(shù)為:2
ConcreteHandlerB 處理了 李四 的請(qǐng)假申請(qǐng),天數(shù)為:5
ConcreteHandlerC 處理了 王五 的請(qǐng)假申請(qǐng),天數(shù)為:10
這只是一個(gè)簡(jiǎn)單示例,實(shí)際使用時(shí)可以根據(jù)業(yè)務(wù)需求進(jìn)行適當(dāng)?shù)臄U(kuò)展和修改。
在使用責(zé)任鏈模式時(shí),需要注意以下幾點(diǎn):
- 確定責(zé)任鏈中的處理順序:要確保責(zé)任鏈中處理者的順序是正確的,以便能夠按照預(yù)期處理請(qǐng)求。處理者的順序可以在創(chuàng)建責(zé)任鏈時(shí)進(jìn)行設(shè)置。
- 避免出現(xiàn)循環(huán)引用:如果責(zé)任鏈中的處理者之間出現(xiàn)了循環(huán)引用,可能會(huì)導(dǎo)致請(qǐng)求無(wú)法被正確處理或進(jìn)入死循環(huán)。因此,在設(shè)置下一個(gè)處理者時(shí)要注意避免出現(xiàn)循環(huán)引用的情況。
- 處理者的數(shù)量控制:在設(shè)計(jì)責(zé)任鏈時(shí)要注意控制處理者的數(shù)量,避免責(zé)任鏈過(guò)長(zhǎng)導(dǎo)致性能下降。可以根據(jù)實(shí)際需求合理劃分責(zé)任鏈,將相關(guān)的處理邏輯放在同一個(gè)處理者中,可以在 Handler 中設(shè)置一個(gè)最大節(jié)點(diǎn)數(shù)量,在 setNextChain() 方法中判斷是否已經(jīng)是超過(guò)其閾值,超過(guò)則不允許該鏈建立,避免無(wú)意識(shí)地破壞系統(tǒng)性能。
通過(guò)建造者模式優(yōu)化
我們可以通過(guò)建造者模式來(lái)創(chuàng)建責(zé)任鏈中的處理者對(duì)象。這種優(yōu)化可以使責(zé)任鏈的創(chuàng)建和配置更加靈活和可拓展,符合開(kāi)閉原則。
優(yōu)化后的示例代碼:
/**
* <p>
* 責(zé)任鏈模式——抽象類處理器
* </p>
*/
public abstract class AbstractHandler {
/**
* 責(zé)任鏈中的下一個(gè)元素
*/
protected AbstractHandler nextHandler;
private void setNextChain(AbstractHandler nextHandler) {
this.nextHandler = nextHandler;
}
/**
* 責(zé)任鏈處理邏輯
*/
public void linkChain(LeaveRequest request) {
handler(request);
//這里還可以加入其他方法
if (Objects.nonNull(nextHandler)) {
nextHandler.linkChain(request);
}
}
/**
* 抽象方法
*/
protected abstract void handler(LeaveRequest request);
public static class Builder {
private AbstractHandler head;
private AbstractHandler tail;
public Builder addHandler(AbstractHandler handler) {
if (this.head == null) {
this.head = this.tail = handler;
return this;
}
this.tail.setNextChain(handler);
this.tail = handler;
return this;
}
public AbstractHandler build() {
return this.head;
}
}
}
public class ChainPatternDemo {
private static AbstractHandler getChainOfHandler() {
return new AbstractHandler.Builder()
.addHandler(new Handler1())
.addHandler(new Handler2())
.addHandler(new Handler3())
.build();
}
public static void main(String[] args) {
AbstractHandler chain = getChainOfHandler();
LeaveRequest request1 = new LeaveRequest("張三", 2);
chain.linkChain(request1);
LeaveRequest request2 = new LeaveRequest("李四", 5);
chain.linkChain(request2);
LeaveRequest request3 = new LeaveRequest("王五", 10);
chain.linkChain(request3);
}
}
在客戶端代碼中,我們使用建造者模式創(chuàng)建了一個(gè)包含多個(gè)處理者的責(zé)任鏈,并發(fā)送了一個(gè)請(qǐng)假申請(qǐng)。責(zé)任鏈會(huì)按照添加處理者的順序依次處理請(qǐng)假申請(qǐng),直到找到能夠處理該請(qǐng)求的處理者為止。
通過(guò)調(diào)用 addHandler 方法,我們可以逐步構(gòu)建責(zé)任鏈,將處理者添加到責(zé)任鏈的末尾,由于 setNextChain() 不對(duì)外調(diào)用,作用域可以更改為 private,最后,通過(guò)調(diào)用 build 方法,我們可以獲取責(zé)任鏈的起始處理者。
Spring中使用責(zé)任鏈模式
Spring中我們可以使用 @Component,@Order 注解,來(lái)讓容器幫我們自動(dòng)構(gòu)建責(zé)任鏈,從而簡(jiǎn)化代碼。
public abstract class Handler {
abstract void handler(LeaveRequest request);
}
@Order(value = 1)
@Component
@Slf4j
public class HandlerA extends Handler{
@Override
public void handler(LeaveRequest request) {
if (request.getDays() <= 3) {
log.info("ConcreteHandlerA 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
@Order(value = 2)
@Component
@Slf4j
public class HandlerB extends Handler {
@Override
public void handler(LeaveRequest request) {
if (request.getDays() > 3 && request.getDays() <= 7) {
log.info("ConcreteHandlerB 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
@Order(value = 3)
@Component
@Slf4j
public class HandlerC extends Handler{
@Override
public void handler(LeaveRequest request) {
if (request.getDays() > 7) {
log.info("ConcreteHandlerC 處理了 " + request.getEmployee() + " 的請(qǐng)假申請(qǐng),天數(shù)為:" + request.getDays());
}
}
}
測(cè)試:
@Test
public void test() {
for (Handler handler : handlerChain) {
LeaveRequest request = new LeaveRequest("王五", 10);
handler.handler(request);
}
}
輸出:
ConcreteHandlerC 處理了 王五 的請(qǐng)假申請(qǐng),天數(shù)為:10
這種寫(xiě)法有其利弊,優(yōu)點(diǎn)是可以避免繁瑣的責(zé)任鏈構(gòu)建過(guò)程,簡(jiǎn)化了代碼結(jié)構(gòu);缺點(diǎn)是具體處理者類之間的執(zhí)行順序不夠直觀,具體使用時(shí)需要權(quán)衡考慮。
責(zé)任鏈模式的優(yōu)點(diǎn)在于其低耦合性、靈活性和可擴(kuò)展性,使得我們能夠更加輕松地管理和組織復(fù)雜的處理流程。然而,也要注意其缺點(diǎn),即請(qǐng)求未必被處理和對(duì)處理順序敏感的特點(diǎn)。
最重要的是,在實(shí)際應(yīng)用中根據(jù)具體需求合理運(yùn)用責(zé)任鏈模式,結(jié)合其他設(shè)計(jì)模式,以便在代碼結(jié)構(gòu)和可維護(hù)性上取得更好的效果。