引入責任鏈模式
責任鏈模式
顧名思義,責任鏈模式(Chain of Responsibility Pattern)爲請求創建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬於行爲型模式。在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。
介紹
意圖:避免請求發送者與接收者耦合在一起,讓多個對象都有可能接收請求,將這些對象連接成一條鏈,並且沿着這條鏈傳遞請求,直到有對象處理它爲止。
主要解決:職責鏈上的處理者負責處理請求,客戶只需要將請求發送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的發送者和請求的處理者解耦了。
何時使用:在處理消息的時候以過濾很多道。
如何解決:攔截的類都實現統一接口。
關鍵代碼:Handler 裏面聚合它自己,在 HanleRequest 裏判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。
應用實例: 1、紅樓夢中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。
優點: 1、降低耦合度。它將請求的發送者和接收者解耦。 2、簡化了對象。使得對象不需要知道鏈的結構。 3、增強給對象指派職責的靈活性。通過改變鏈內的成員或者調動它們的次序,允許動態地新增或者刪除責任。 4、增加新的請求處理類很方便。
缺點: 1、不能保證請求一定被接收。 2、系統性能將受到一定影響,而且在進行代碼調試時不太方便,可能會造成循環調用。 3、可能不容易觀察運行時的特徵,有礙於除錯。
使用場景: 1、有多個對象可以處理同一個請求,具體哪個對象處理該請求由運行時刻自動確定。 2、在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。 3、可動態指定一組對象處理請求。
注意事項:在 JAVA WEB 中遇到很多應用。
Support是一個抽象類,他的核心方法support中,如果當前support可以解決,就解決,如果不行,就交給next去解決。
package cn.chinotan.service.designpattern.command.ChainOfResponsibility;
/**
* @program: test
* @description: 當前節點可以解決就解決,否則交給下一個節點
* @author: xingcheng
* @create: 2018-09-16 16:41
**/
public abstract class Support {
/**
* 處理節點名稱
*/
private String name;
/**
* 下一個處理節點
*/
private Support next;
public Support(String name) {
this.name = name;
}
public final void support(Problem problem){
if (resolve(problem)){
success(problem);
} else if (next != null){
next.support(problem);
} else {
fail(problem);
}
}
/**
* 進行解決
* @param problem 待解決的問題
* @return
*/
protected abstract Boolean resolve(Problem problem);
/**
* 解決失敗
* @param problem 待解決的問題
*/
protected void fail(Problem problem){
System.out.println(problem + "無法解決");
}
/**
* 成功解決
* @param problem 待解決的問題
*/
protected void success(Problem problem){
System.out.println(problem + "解決成功} by " + this);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("[name='").append(name).append("]");
return sb.toString();
}
public Support setNext(Support next) {
this.next = next;
return next;
}
}
然後我們實現幾個具體的support類
NoSupport類是一個永遠不解決問題的類
package cn.chinotan.service.designpattern.command.ChainOfResponsibility;
/**
* @program: test
* @description: 永遠不解決問題的節點
* @author: xingcheng
* @create: 2018-09-16 17:02
**/
public class NotSupport extends Support{
public NotSupport(String name) {
super(name);
}
@Override
protected Boolean resolve(Problem problem) {
return false;
}
}
LimitSupport類,解決指定範圍內的問題
package cn.chinotan.service.designpattern.command.ChainOfResponsibility;
/**
* @program: test
* @description: 解決部分範圍的問題
* @author: xingcheng
* @create: 2018-09-16 17:15
**/
public class LimitSupport extends Support {
private Integer limit;
public LimitSupport(Integer limit, String name) {
super(name);
this.limit = limit;
}
@Override
protected Boolean resolve(Problem problem) {
if (problem.getCount() < limit){
return true;
} else {
return false;
}
}
}
EvenSupport類,解決偶數的問題
package cn.chinotan.service.designpattern.command.ChainOfResponsibility;
/**
* @program: test
* @description: 只解決部分問題的節點
* @author: xingcheng
* @create: 2018-09-16 17:02
**/
public class EvenSupport extends Support {
public EvenSupport(String name) {
super(name);
}
@Override
protected Boolean resolve(Problem problem) {
if ((problem.getCount() & 1) == 0){
return true;
} else {
return false;
}
}
}
啓動測試類
package cn.chinotan.service.designpattern.command.ChainOfResponsibility;
/**
* @program: test
* @description: 測試啓動類
* @author: xingcheng
* @create: 2018-09-16 17:28
**/
public class DemoTest {
public static void main(String[] args) {
NotSupport noSupport = new NotSupport("noSupport");
LimitSupport limitSupport100 = new LimitSupport(100, "limitSupport100");
LimitSupport limitSupport200 = new LimitSupport(200, "limitSupport200");
LimitSupport limitSupport300 = new LimitSupport(300, "limitSupport300");
EvenSupport evenSupport = new EvenSupport("evenSupport");
SpecialSupport specialSupport = new SpecialSupport(363, "specialSupport");
noSupport.setNext(limitSupport100).setNext(limitSupport200).setNext(limitSupport300).setNext(evenSupport).setNext(specialSupport);
for (int i = 0; i < 500; i+= 33){
noSupport.support(new Problem(i));
}
}
}
Main類中定義了一個責任鏈,將幾個support對象連接在一起,組成了一條責任鏈,然後去處理問題
運行結果如下:
責任鏈模式的分析
首先,責任鏈模式中,存在着這麼幾個角色:
-
Handler處理者
handler金額use定義了處理請求的接口,handler知道,下一個處理者是誰,如果自己無法處理請求,就轉給下一個處理者。
在實例中對應的是,support類和support方法 -
concreteHandler(具體的處理者)
具體的處理者是處理請求的具體角色。
在此實例中,由NotSupport角色和其他幾個類扮演 -
Client
請求者角色,就是向第一個具體的handler發送請求的角色,並連接好責任鏈,實例中對應的是main類的main方法。
責任鏈的作用
-
弱化了發出請求的人和處理請求的人之間的關係
發出請求的人只需要向第一個具體的處理者發送請求,然後就可以不用管了,處理者會在責任鏈上自己尋找處理的方法。
這樣就解耦了處理者和請求者之間的關係。
如果我們不採取責任鏈模式,那麼請求者就必須要很清楚哪個處理者能處理它的請求,就必須對所有的處理者都有所瞭解,類似於上帝視角,然而在實際中,要求請求這瞭解這麼多是不實際的 -
可以動態的改變責任鏈
責任鏈還有的好處就是可以動態的改變責任,刪除或者添加或者改變順序。 -
讓各個處理者專注於實現自己的職責
責任鏈模式同時還做到了處理者之間的解耦,處理者自己專注於自己的處理邏輯就好,不管其他處理者幹什麼。 -
推卸責任也可能導致處理延遲
我們可以責任鏈模式需要在責任鏈上傳播責任,直至找到合適的處理對象。這樣提高了程序的靈活性,但同時也出現了處理的延遲,因爲有一個尋找的過程。所以需要低延遲的情況下,就不應該使用責任鏈模式
責任鏈模式的應用
1、servlet中的Filter,servlet中分別定義了一個 Filter和FilterChain的接口,定義一個Chain,裏面包含了Filter列表和servlet,達到在調用真正servlet之前進行各種filter邏輯
2、Dubbo中的Filter,Dubbo在創建Filter的時候是另外一個方法,通過把Filter封裝成 Invoker的匿名類,通過鏈表這樣的數據結構來完成責任鏈,Dubbo的責任鏈就沒有類似FilterChain這樣的類把Filter和調用Invoker結合起來,而是通過創建一個鏈表,調用的時候我們只知道第一個節點,每個節點包含了下一個調用的節點信息。 這裏的雖然Invoker封裝Filter沒有顯示的指定next,但是通過java匿名類和final的機制達到同樣的效果
3、Mybatis中的Plugin,Mybatis可以配置各種Plugin,無論是官方提供的還是自己定義的,Plugin和Filter類似,就在執行Sql語句的時候做一些操作。Mybatis的責任鏈則是通過動態代理的方式,使用Plugin代理實際的Executor類。(這裏實際還使用了組合模式,因爲Plugin可以嵌套代理)
責任鏈的優點和缺點
優點:實現了請求者與處理者代碼分離:發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織和分配責任。提高系統的靈活性和可擴展行。
缺點:每次都是從鏈頭開始:這也正是鏈表的缺點。你也許會想到一種貌似不錯的解決方案,比如使用hash映射,將要處理的請求id與處理類對象關聯,但是這樣系統損失了可擴展性。