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