1. 程式人生 > >簡說設計模式——職責鏈模式

簡說設計模式——職責鏈模式

一、什麼是職責鏈模式

  從文字角度出發,我們可以先將關注點放在“鏈”字上,很容易聯想到鏈式結構,舉個生活中常見的例子,擊鼓傳花遊戲就是一個很典型的鏈式結構,所有人形成一條鏈,相互傳遞。而從另一個角度說,職責鏈就是所謂的多級結構,比如去醫院開具病假條,普通醫生只能開一天的證明,如果需要更多時常,則需將開具職責轉交到上級去,上級醫師只能開三天證明,如需更多時常,則需將職責轉交到他的上級,以此類推,這就是一個職責鏈模式的典型應用。再比如公司請假,根據請假時常的不同,需要遞交到的級別也不同,這種層級遞進的關係就是一種多級結構。

  職責鏈模式(Chain Of Responsibility),使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係。將這個物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。UML結構圖如下:

  其中,Handler是抽象處理者,定義了一個處理請求的介面;ConcreteHandler是具體處理者,處理它所負責的請求,可訪問它的後繼者,如果可處理該請求就處理,否則就將該請求轉發給它的後繼者。

  1. 抽象處理者

  抽象處理者實現了三個職責:

  • 定義一個請求的處理方法handlerMessage(),是唯一對外開放的方法
  • 定義一個鏈的編排方式setNext(),用於設定下一個處理者
  • 定義了具體的請求者必須實現的兩個方法,即定義自己能夠處理的級別的getHandlerLevel()方法及具體的處理任務echo()方法
 1 public abstract class Handler {
 2     
 3     private Handler nextHandler;    //下一個處理者
 4     
 5     public final Response handlerMessage(Request request) {
 6         Response response = null;
 7         
 8         if(this.getHandlerLevel().equals(request.getRequestLevel())) {    //判斷是否是自己的處理級別
 9             response = this.echo(request);
10         } else {
11             if(this.nextHandler != null) {    //下一處理者不為空
12                 response = this.nextHandler.handlerMessage(request);
13             } else {
14                 //沒有適當的處理者,業務自行處理
15             }
16         }
17         
18         return response;
19     }
20     
21     //設定下一個處理者
22     public void setNext(Handler handler) {
23         this.nextHandler = handler;
24     }
25     
26     //每個處理者的處理等級
27     protected abstract Level getHandlerLevel();
28     
29     //每個處理者都必須實現的處理任務
30     protected abstract Response echo(Request request);
31 
32 }

  2. 具體處理者

  這裡我們定義三個具體處理者,以便能組成一條鏈,ConcreteHandlerB及ConcreteHandlerC就不再贅述了。

 1 public class ConcreteHandlerA extends Handler {
 2 
 3     @Override
 4     protected Level getHandlerLevel() {
 5         //設定自己的處理級別
 6         return null;
 7     }
 8 
 9     @Override
10     protected Response echo(Request request) {
11         //完成處理邏輯
12         return null;
13     }
14 
15 }

  3. Level類

  Level類負責定義請求和處理級別,具體內容需根據業務產生。

1 public class Level {
2     //定義一個請求和處理等級
3 }

  4. Request類

  Request類負責封裝請求,具體內容需根據業務產生。

1 public class Request {
2     
3     //請求的等級
4     public Level getRequestLevel() {
5         return null;
6     }
7     
8 }

  5. Response類

  Response類負責封裝鏈中返回的結果,具體內容需根據業務產生。

1 public class Response {
2     //處理者返回的資料
3 }

  6. Client客戶端

  我們在場景類或高層模組中對類進行組裝,並傳遞請求,返回結果。如下對三個具體處理者進行組裝,按照1→2→3的順序,並得出返回結果。

 1 public class Client {
 2     
 3     public static void main(String[] args) {
 4         Handler handler1 = new ConcreteHandlerA();
 5         Handler handler2 = new ConcreteHandlerB();
 6         Handler handler3 = new ConcreteHandlerC();
 7         
 8         //設定鏈中的階段順序 1->2->3
 9         handler1.setNext(handler2);
10         handler2.setNext(handler3);
11         
12         //提交請求返回結果
13         Response response = handler1.handlerMessage(new Request());
14     }
15 
16 }

  當然這是個未完成的模板,最終結果會因為 request.getRequestLevel() 為空而丟擲異常,具體內容需根據業務邏輯進行編寫。

 二、職責鏈模式的應用

  1. 何時使用

  • 處理訊息時

  2. 方法

  • 攔截的類都實現同一介面

  3. 優點

  • 將請求和處理分開,實現解耦,提高系統的靈活性
  • 簡化了物件,使物件不需要知道鏈的結構

  4. 缺點

  • 效能會收到影響,特別是在鏈比較長的時候
  • 除錯不方便。採用了類似遞迴的方式,除錯時邏輯可能比較複雜
  • 不能保證請求一定被接收

  5. 使用場景

  • 有多個物件可以處理同一個請求
  • 在不明確指定接收者的情況下,向多個物件中的提交請求
  • 可動態指定一組物件處理請求

  6. 應用例項

  • 多級請求
  • 擊鼓傳花
  • 請假/加薪請求
  • Java Web中Tomcat對Encoding的處理、攔截器

  7. 注意事項

  • 需控制鏈中最大節點數量,一般通過在Handler中設定一個最大節點數量,在setNext()方法中判斷是否已經超過閥值,超過則不允許該鏈建立,避免出現超長鏈無意識地破壞系統性能

三、職責鏈模式的實現

  我們就以請假/加薪為例,實現一個較為簡單的職責鏈模式。UML圖如下:

  1. 抽象管理者

  通過Manager抽象類管理所有管理者,setSuperior()方法用於定義職責鏈的下一級,即定義當前管理者的上級。

 1 public abstract class Manager {
 2     
 3     protected String name;
 4     protected Manager superior;    //管理者的上級
 5     
 6     public Manager(String name) {
 7         this.name = name;
 8     }
 9     
10     //設定管理者的上級
11     public void setSuperior(Manager superior) {
12         this.superior = superior;
13     }
14     
15     //申請請求
16     public abstract void handlerRequest(Request request);
17 
18 }

  2. 具體管理者

  經理類如下,只可批准兩天以內的假期,其餘請求將繼續申請上級。

 1 public class CommonManager extends Manager {
 2 
 3     public CommonManager(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void handlerRequest(Request request) {
 9         if (request.getRequestType().equals("請假") && request.getNumber() <= 2) {    //只能批准兩天內的假期
10             System.out.println(name + ":" + request.getRequestContent() + ",時長:" + request.getNumber() + "天,被批准");
11         } else {    //其餘請求申請上級
12             if (superior != null) {
13                 superior.handlerRequest(request);
14             }
15         }
16     }
17 
18 }

  總監類如下,只可批准五天以內的假期,其餘請求將繼續申請上級。

 1 public class Majordomo extends Manager {
 2 
 3     public Majordomo(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void handlerRequest(Request request) {
 9         if (request.getRequestType().equals("請假") && request.getNumber() <= 5) {    //只能批准五天內的假期
10             System.out.println(name + ":" + request.getRequestContent() + ",時長:" + request.getNumber() + "天,被批准");
11         } else {    //其餘請求申請上級
12             if (superior != null) {
13                 superior.handlerRequest(request);
14             }
15         }        
16     }
17 
18 }

  總經理類,可以批准任意時常的假期,並且可以批准是否加薪。

 1 public class GeneralManager extends Manager {
 2 
 3     public GeneralManager(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void handlerRequest(Request request) {
 9         if (request.getRequestType().equals("請假")) {    //能批准任意時長的假期
10             System.out.println(name + ":" + request.getRequestContent() + ",時長:" + request.getNumber() + "天,被批准");
11         } else if (request.getRequestType().equals("加薪") && request.getNumber() <= 500) {
12             System.out.println(name + ":" + request.getRequestContent() + ",金額:¥" + request.getNumber() + ",被批准");
13         } else if (request.getRequestType().equals("加薪") && request.getNumber() > 500) {
14             System.out.println(name + ":" + request.getRequestContent() + ",金額:¥" + request.getNumber() + ",再說吧");
15         }
16     }
17 
18 }

  3. 申請類

 1 public class Request {
 2     
 3     private String requestType;    //申請類別
 4     private String requestContent;    //申請內容
 5     private int number;    //數量
 6 
 7     public String getRequestType() {
 8         return requestType;
 9     }
10 
11     public void setRequestType(String requestType) {
12         this.requestType = requestType;
13     }
14 
15     public String getRequestContent() {
16         return requestContent;
17     }
18 
19     public void setRequestContent(String requestContent) {
20         this.requestContent = requestContent;
21     }
22 
23     public int getNumber() {
24         return number;
25     }
26 
27     public void setNumber(int number) {
28         this.number = number;
29     }
30     
31 }

   4. Client客戶端

  下面測試幾組資料。

 1 public class Client {
 2     
 3     public static void main(String[] args) {
 4         CommonManager commonManager = new CommonManager("尼古拉斯·經理");
 5         Majordomo majordomo = new Majordomo("尼古拉斯·總監");
 6         GeneralManager generalManager = new GeneralManager("尼古拉斯·總經理");
 7         
 8         //設定上級
 9         commonManager.setSuperior(majordomo);
10         majordomo.setSuperior(generalManager);
11         
12         Request request = new Request();
13         request.setRequestType("請假");
14         request.setRequestContent("adam請假");
15         request.setNumber(1);
16         commonManager.handlerRequest(request);
17         
18         Request request2 = new Request();
19         request2.setRequestType("請假");
20         request2.setRequestContent("adam請假");
21         request2.setNumber(4);
22         commonManager.handlerRequest(request2);
23         
24         Request request3 = new Request();
25         request3.setRequestType("加薪");
26         request3.setRequestContent("adam請求加薪");
27         request3.setNumber(500);
28         commonManager.handlerRequest(request3);
29         
30         Request request4 = new Request();
31         request4.setRequestType("加薪");
32         request4.setRequestContent("adam請求加薪");
33         request4.setNumber(1000);
34         commonManager.handlerRequest(request4);
35     }
36 
37 }

   執行結果如下:

  

 

  原始碼地址:https://gitee.com/adamjiangwh/GoF