一、引言
今天我們開始講“行為型”設計模式的第八個模式,該模式是【職責鏈模式】,英文名稱是:Chain of Responsibility Pattern。讓我們看看現實生活中的例子吧,理解起來可能更容易。我們看看某公司的采購流程吧。某公司的規章制度規定,采購原材料的總價在5萬之內,只需要經理級別的人批准即可,采購總價大於5萬小於10萬的則需要財務經理進行批准,總價大於10萬小於30萬的需要總經理批准,而總價大於30萬的則需要通過董事會會議討論決定。對於這樣一個需求,最直接的方法就是設計一個方法,該方法接受的參數是采購的總價,然后在這個方法內對價格進行判斷,然后針對不同的條件交給不同級別的角色去處理,如果情況就是這樣,不變了,這樣做很好,沒問題。如果我們又有新的條件要增加該怎么辦呢?我們不得不去修改原來設計的方法來再添加一個條件判斷,讓本已多重if-else判斷語句更多了,這樣的設計顯然違背了“開放-關閉”原則。這時候,我們可以采用職責鏈模式來解決這樣的問題。
二、職責鏈模式的詳細介紹
2.1、動機(Motivate)
在軟件構建過程中,一個請求可能被多個對象處理,但是每個請求在運行時只能有一個接受者,如果顯示指定,將必不可少地帶來請求發送者與接受者的緊耦合。如何使請求的發送者不需要指定具體的接受者,讓請求的接受者自己在運行時決定來處理請求,從而使兩者解耦。
2.2、意圖(Intent)
避免請求發送者與接收者耦合在一起,讓多個對象都有可能接受請求,將這些對象連接成一條鏈,並且沿着這條鏈傳遞請求,知道有對象處理它為止。 ——《設計模式》GoF
2.3、結構圖(Structure)
2.4、模式的組成
可以看出,在職責鏈模式的結構圖有以下角色:
(1)、抽象處理者角色(Handler):抽象處理者定義了一個處理請求的接口,它一般設計為抽象類,由於不同的具體處理者處理請求的方式不同,因此在其中定義了抽象請求處理方法。因為每一個處理者的下家還是一個處理者,因此在抽象處理者中定義了一個自類型的對象,作為其對下家的引用。通過該引用,處理者可以連成一條鏈。
(2)、具體處理者角色(ConcreteHandler):具體處理者是抽象處理者的子類,它可以處理用戶請求,在具體處理者類中實現了抽象處理者中定義的抽象處理方法,在處理請求之前需要進行判斷,看是否有相應的處理權限,如果可以處理請求就處理它,否則將請求轉發給后繼者;在具體處理者中可以訪問鏈中下一個對象,以便請求的轉發。
2.5、職責鏈模式的代碼實現
在現實生活中,職責鏈模式的例子也是很多的,例如:公司的請假流程就是一個很好的職責鏈模式的例子,如果請假半天,只要告訴本部門經理就可以了;如果請假7天或者以上必須人事總監批准;如果請假15天以上,那就要經過總裁批准了。還有類似的例子就是采購的流程,其流程也是職責鏈模式很好的體現,采購的金額不同,需要批准的人員也不同,比如:部門采購1萬元的紙品,只要部門領導簽批就可以,如果要采購大於1萬小於5萬的物品,那就需要財務經理簽批了,如果采購30萬的原材料或者物品,那就需要總裁或者類似角色才能審批了。接下來我們就以采購的實例來說明職責鏈模式。實現代碼如下:
1 namespace ChainOfResponsibility 2 { 3 // 采購請求 4 public sealed class PurchaseRequest 5 { 6 // 金額 7 public double Amount { get; set; } 8 9 // 產品名字 10 public string ProductName { get; set; } 11 12 public PurchaseRequest(double amount, string productName) 13 { 14 Amount = amount; 15 ProductName = productName; 16 } 17 } 18 19 //抽象審批人,Handler---相當於“抽象處理者角色” 20 public abstract class Approver 21 { 22 //下一位審批人,由此形成一條鏈 23 public Approver NextApprover { get; set; } 24 25 //審批人的名稱 26 public string Name { get; set; } 27 28 public Approver(string name) 29 { 30 this.Name = name; 31 } 32 33 //處理請求 34 public abstract void ProcessRequest(PurchaseRequest request); 35 } 36 37 //部門經理----相當於“具體處理者角色” ConcreteHandler 38 public sealed class Manager : Approver 39 { 40 public Manager(string name): base(name){ } 41 42 public override void ProcessRequest(PurchaseRequest request) 43 { 44 if (request.Amount <= 10000.0) 45 { 46 Console.WriteLine("{0} 部門經理批准了對原材料{1}的采購計划!", this.Name, request.ProductName); 47 } 48 else if (NextApprover != null) 49 { 50 NextApprover.ProcessRequest(request); 51 } 52 } 53 } 54 55 //財務經理---相當於“具體處理者角色”ConcreteHandler 56 public sealed class FinancialManager : Approver 57 { 58 public FinancialManager(string name): base(name){ } 59 60 public override void ProcessRequest(PurchaseRequest request) 61 { 62 if (request.Amount > 10000.0 && request.Amount <= 50000.0) 63 { 64 Console.WriteLine("{0} 財務經理批准了對原材料{1}的采購計划!", this.Name, request.ProductName); 65 } 66 else if (NextApprover != null) 67 { 68 NextApprover.ProcessRequest(request); 69 } 70 } 71 } 72 73 //總裁---相當於“具體處理者角色” ConcreteHandler 74 public sealed class CEO :Approver 75 { 76 public CEO(string name): base(name){ } 77 78 public override void ProcessRequest(PurchaseRequest request) 79 { 80 if (request.Amount > 50000.0 && request.Amount < 300000.0) 81 { 82 Console.WriteLine("{0} 總裁批准了對原材料 {1} 的采購計划!", this.Name, request.ProductName); 83 } 84 else 85 { 86 Console.WriteLine("這個采購計划的金額比較大,需要一次董事會會議討論才能決定!"); 87 } 88 } 89 } 90 91 class Program 92 { 93 static void Main(string[] args) 94 { 95 PurchaseRequest requestDao = new PurchaseRequest(8000.0, "單刀5把"); 96 PurchaseRequest requestHuaJi = new PurchaseRequest(10000.0, "10把方天畫戟"); 97 PurchaseRequest requestJian = new PurchaseRequest(80000.0, "5把金絲龍鱗閃電劈"); 98 99 Approver manager = new Manager("黃飛鴻"); 100 Approver financial = new FinancialManager("黃麒英"); 101 Approver ceo = new CEO("十三姨"); 102 103 // 設置職責鏈 104 manager.NextApprover = financial; 105 financial.NextApprover = ceo; 106 107 // 處理請求 108 manager.ProcessRequest(requestDao); 109 manager.ProcessRequest(requestHuaJi); 110 manager.ProcessRequest(requestJian); 111 112 Console.ReadLine(); 113 } 114 } 115 }
模式的代碼如上,很簡單,備注很清楚,慢慢品味一下就知道其中道理了。
三、職責鏈模式的實現要點:
Chain of Responsibility模式的應用場合在於“一個請求可能有多個接受者,但是最后真正的接受者只有一個”,只有這時候請求發送者與接受者的耦合才有可能出現“變化脆弱”的症狀,職責鏈的目的就是將二者解耦,從而更好地應對變化。
應用了Chain of Responsibility模式后,對象的職責分派將更具靈活性。我們可以在運行時動態添加/修改請求的處理職責。
當我們要新增一個DHandler處理請求,就不需再改原來的代碼了,遵從了開放封閉原則。這樣我們的程序就更賦予變化,更有變化的抵抗力。Handler類本身繼承自BaseHandler類型,又包含了一個BaseHandler類型的對象,這點類似Decorator模式。
如果請求傳遞到職責鏈的末尾仍得不到處理,應該有一個合理的缺省機制。這也是每一個接受對象的責任,而不是發出請求的對象的責任。
(1)、職責鏈模式的主要優點有:
1】、降低耦合度:職責鏈模式使得一個對象無需知道是其他哪一個對象處理其請求。對象僅需知道該請求會被處理即可,接受者和發送者都沒有對方的明確信息,且鏈中的對象不需要知道鏈的結構,有客戶端負責鏈的創建。
2】、可簡化對象的相互連接:接受者對象僅需維持一個指向其后繼者的引用,而不需維持它對所有的候選處理者的引用。
3】、增強給對象指派職責的靈活性:在給對象分派職責時,職責鏈可以給我們帶來更多的靈活性。可以通過在運行時對該連進行動態的增加或修改處理一個請求的職責。
4】、增加新的請求處理類很方便:在系統中增加一個新的請求處理者無需修改原有系統的代碼,只需要在客戶端重新建鏈即可,從這一點看來是符合“開閉原則”的。
(2)、職責鏈模式的主要缺點有:
1】、在找到正確的處理對象之前,所有的條件判定都要執行一遍,當責任鏈過長時,可能會引起性能的問題。
2】、可能導致某個請求不被處理。
3】、客戶端需要組裝這個鏈條,耦合了客戶端和鏈條的組成結構,可以把這個在客戶端的組合動作提到外面,通過配置來做,會更好點。
(3)、在下面的情況下可以考慮使用職責鏈模式:
1】、一個系統的審批需要多個對象才能完成處理的情況下,例如請假系統等。
2】、代碼中存在多個if-else語句的情況下,此時可以考慮使用責任鏈模式來對代碼進行重構
3】、有多個對象可以處理同一個請求,具體哪個對象處理該請求有運行時刻自動確定。客戶端只需將請求提交到鏈上,無須關心請求的處理對象是誰以及它是如何處理的。
4】、不明確指定接受者的情況下,向多個對象中的一個提交一個請求。請求的發送者與請求者解耦,請求將沿着鏈進行傳遞,尋求響應的處理者。
5】、可動態指定一組對象處理請求。客戶端可以動態創建職責鏈來處理請求,還可以動態改變鏈中處理者之間的先后次序
四、.NET 職責鏈模式的實現
這個模式在Net框架中的實現不多,我感覺這個模式的使用場景更多的是在業務系統總才會有更大的用處。這種模式在處理UI的消息時很常用,但實際上Windows消息循環還是硬編碼的結構。因為效率上的考慮,Windows消息循環是哪個對象有一個請求,則直接到達處理函數的地址。如果鏈條上的對象多了,而真正處理的函數在鏈條后部分,效率會很低下。因此我們在使用這種模式的時候更適合業務流程,即對性能要求不是特別高的情況更加常用。
五、總結
終於寫完了,這個模式並不是很難,在現實生活中也能很容易的找到對應的實例。這個模式也是為了解耦,解耦請求的發送者和接受者,當有新的需求的時候更容易變化,讓我們的代碼更符合面向對象OO的設計。