引子
一個事件需要經過多個物件處理是一個挺常見的場景,譬如採購審批流程,請假流程,軟體開發中的異常處理流程,web請求處理流程等各種各樣的流程,可以考慮使用責任鏈模式來實現。
現在以請假流程為例,一般公司普通員工的請假流程簡化如下:
普通員工發起一個請假申請,當請假天數小於3天時只需要得到主管批准即可;當請假天數大於3天時,主管批准後還需要提交給經理審批,經理審批通過,若請假天數大於7天還需要進一步提交給總經理審批。
簡單的流程可以通過 if-else 即可實現:
public class Leave { public void leaveApproval(int leaveDays) { if (leaveDays < 3) { Console.WriteLine("專案經理審批"); } else if (leaveDays < 7) { Console.WriteLine("部門經理審批"); } else if (leaveDays < 30) { Console.WriteLine("總經理審批"); } else { Console.WriteLine("審批困難"); } } }
但是這樣的寫法看起來簡單,後續維護難度卻是不少。可以看出程式碼臃腫且耦合度高。
- 程式碼臃腫:實際應用中的判定條件通常不是這麼簡單地判斷,也許需要複雜的計算,也許需要查詢資料庫等等,這就會有很多額外的程式碼,如果判斷條件再比較多,那麼這個if…else…語句基本上就沒法看了。
- 耦合度高:如果我們想繼續新增處理請求的類,那麼就要繼續新增else if判定條件;另外,這個條件判定的順序也是寫死的,如果想改變順序,那麼也只能修改這個條件語句。
在設計模式中提倡單一職責原則,如果專案組內再加一個組長,審批請假小於一天的呢?此時就會感覺 if-else 靈活性太差,修改程式碼後測試需要重新測試全部流程才能保證質量。
既然已經清楚他的不足,則針對此業務邏輯可以稍作轉換:如果滿足條件1,則由 Handler1 來處理,不滿足則向下傳遞;如果滿足條件2,則由 Handler2 來處理,不滿足則繼續向下傳遞,以此類推,直到條件結束。其實改進的方法也很簡單,就是把判定條件的部分放到處理類中,這就是責任連模式的原理。
定義
責任鏈模式屬於行為類模式。使多個物件都有機會處理請求,從而避免了請求的傳送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。
責任鏈模式把多個處理器串成鏈,然後讓請求在鏈上傳遞:
類圖
從的定義可以看出涉及的物件只有處理者角色,但可以有多個處理者,這些處理者做的事情都是一樣的,處理請求的方法,所以可以抽象出一個處理者角色進行程式碼複用。如下類圖。
角色
- Handler(抽象處理類):抽象處理類中主要包含一個指向下一處理類的成員變數nextHandler和一個處理請求的方法handRequest,handRequest方法的主要主要思想是,如果滿足處理的條件,則有本處理類來進行處理,否則由nextHandler來處理。
- ConcreteHandler(具體處理類):具體處理類主要是對具體的處理邏輯和處理的適用條件進行實現。
實現
將上面的請假流程重新梳理,使用責任鏈模式進行實現:
using System; namespace 責任鏈模式 { class Program { static void Main(string[] args) { LeaveRequest leaveTwoDays = new LeaveRequest(2, "grey1"); LeaveRequest leaveSixDays = new LeaveRequest(6, "grey2"); LeaveRequest leaveEightDays = new LeaveRequest(8, "grey3"); Approver PM = new Manager("jon1"); Approver DM = new DepartmentManager("jon2"); Approver GM = new GeneralManager("jon3"); // 設定責任鏈 PM.NextApprover = DM; DM.NextApprover = GM; // 處理請求 PM.LeaveRequest(leaveTwoDays); PM.LeaveRequest(leaveSixDays); PM.LeaveRequest(leaveEightDays); Console.ReadLine(); } } // 請假需求 public class LeaveRequest { public int Day { get; set; } public string Name { get; set; } public LeaveRequest(int day, string name) { this.Day = day; this.Name = name; } } // 審批人 public abstract class Approver { public Approver NextApprover { get; set; } public string Name { get; set; } public Approver(string name) { this.Name = name; } public abstract void LeaveRequest(LeaveRequest requeset); } // 專案經理 public class Manager : Approver { public Manager(string name) : base(name) { } public override void LeaveRequest(LeaveRequest requeset) { if (requeset.Day < 3) { Console.WriteLine("專案經理 {0} 審批 {1} 請假", this.Name, requeset.Name); } else { NextApprover.LeaveRequest(requeset); } } } // 部門經理 public class DepartmentManager : Approver { public DepartmentManager(string name) : base(name) { } public override void LeaveRequest(LeaveRequest requeset) { if (requeset.Day < 7) { Console.WriteLine("部門經理 {0} 審批 {1} 請假", this.Name, requeset.Name); } else { NextApprover.LeaveRequest(requeset); } } } // 總經理 public class GeneralManager : Approver { public GeneralManager(string name) : base(name) { } public override void LeaveRequest(LeaveRequest requeset) { if (requeset.Day < 30) { Console.WriteLine("總經理 {0} 審批 {1} 請假", this.Name, requeset.Name); } else { Console.WriteLine("審批困難"); ; } } } }
執行一下:
專案經理 jon1 審批 grey1 請假
部門經理 jon2 審批 grey2 請假
總經理 jon3 審批 grey3 請假
LeaveRequest 類為請求請假。
Approver 為處理人員。並且設定了三個處理人員,Manager、DepartmentManager、GeneralManager。
設定的責任鏈為 Manager-->DepartmentManager-->GeneralManager。當發生請假請求時首先由Manager進行處理,處理不了轉由DepartmentManager,如果DepartmentManager還是處理不了則繼續向更好職位的人員GeneralManager進行提交,由更大許可權的人進行處理。
實現的功能和文章最初的 if...else 一樣。但時可以看到使用責任鏈模式程式碼更清楚,請求傳送者是傳送者,接收者是接收者。
適用場景
通過上面的定義、類圖及示例可以考慮責任鏈模式適用的場景:
- 在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。
- 程式碼中存在多個if-else語句的情況下,此時可以考慮使用責任鏈模式來對程式碼進行重構。
- 可動態指定一組物件處理請求,客戶端可以動態建立職責鏈來處理請求,還可以改變鏈中處理者之間的先後次序。
優缺點
通過上面的介紹很容易發現,責任鏈模式的優點:
- 降低了物件之間的耦合度。該模式使得一個物件無須知道到底是哪一個物件處理其請求以及鏈的結構,傳送者和接收者也無須擁有對方的明確資訊。
- 增強了系統的可擴充套件性。可以根據需要增加新的請求處理類,滿足開閉原則。
- 增強了給物件指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
- 責任鏈簡化了物件之間的連線。每個物件只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if···else 語句。
- 責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個物件完成,明確各類的責任範圍,符合類的單一職責原則。
但也有缺點:
- 不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
- 對比較長的職責鏈,請求的處理可能涉及多個處理物件,系統效能將受到一定影響。
- 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設定而導致系統出錯,如可能會造成迴圈呼叫。
擴充套件
純的責任鏈模式:
- 一個具體處理者物件只能在兩個行為中選擇一個:要麼承擔全部責任,要麼將責任推給下家,不允許出現某一個具體處理者物件在承擔了一部分或全部責任後 又將責任向下傳遞的情況。
- 一個請求必須被某一個處理者物件所接收,不能出現某個請求未被任何一個處理者物件處理的情況。
不純的責任鏈模式:
- 允許某個請求被一個具體處理者部分處理後再向下傳遞。
- 或者一個具體處理者處理完某請求後其後繼處理者可以繼續處理該請求。
- 而且一個請求可以最終不被任何處理者物件所接收。
總結
責任鏈模式其實就是一個靈活版的if…else…語句,將這些判定條件的語句放到了各個處理類中,非常靈活。
責任鏈模式是一種把多個處理器組合在一起,依次處理請求的模式。
責任鏈降低了請求端和接收端之間的耦合,使多個物件都有機會處理某個請求。
責任鏈模式經常用在攔截、預處理請求等。
與此同樣也帶來了風險,比如設定處理類前後關係時,一定要特別仔細,搞對處理類前後邏輯的條件判斷關係,並且注意不要在鏈中出現迴圈引用的問題。
參考
https://juejin.im/post/6844903702260629512
https://www.w3cschool.cn/javadesignpattern/omas1ii2.html
https://www.cnblogs.com/zhili/p/ChainOfResponsibity.html
http://c.biancheng.net/view/1383.html
https://www.liaoxuefeng.com/wiki/1252599548343744/1281319474561057