重温设计模式(三)——职责链模式(chain of responsibility)
一. 寫在前面的
這么多的設計模式,我覺得職責鏈是我第一次看上去最簡單,可是回想起來卻又最復雜的一個模式。
因此,這個文章我醞釀了很久,一直也沒有膽量發出來,例子也是改了又改,可是仍然覺得不夠合理。所以希望各位多多指教。
二. 什么是鏈
文章伊始,先讓我們了解這個最基本的概念,什么是鏈。
我給鏈下了這樣的定義:
1. 鏈是一系列節點的集合。
2. 鏈的各節點可靈活拆分再重組。
三. 何為職責鏈
職責鏈模式:使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系。將這個對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理他為止。
圖如下:
UML很簡單,讓我們先來看一個簡單的例子。
四. 職責鏈模式應用之請假管理
請假這個事情,相信每個人都不陌生。
我們公司是個相對很寬松的公司。
在公司里,如果你的請假時間小于0.5天,那么只需要向項目經理打聲招呼就OK了。
如果超過了0.5天,但是還小于2天,那么就要去找人事部處理,當然,這就要扣工資了。
如果超過了2天,你就需要去找總經理了,工資當然也玩完了。
那么,對于我們來說,這個流程就是這樣的。
也就是這樣一個過程,你需要和你的直接上級——項目經理去打交道,最終可能是項目經理給你回郵件,可能是人事部給你回郵件,也可能是總經理給你回郵件。內部的過程其實應該是個黑盒子,你并不知道內部的消息是如何處理的。你需要找到的,只是你想要第一個交付的對象而已。
那么我們的代碼應該是這樣的。
首先我們要寫一個請求的類。
class Request {private int day;private string reason;public int Day{get { return day; }set { day = value; }}public string Reason{get { return reason; }set { reason = value; }}public Request(int day, string reason){this.day = day;this.reason = reason;} }abstract class Boss {private string name;public string Name{get { return name; }set { name = value; }}private Boss successor;public Boss Successor{get { return successor; }set { successor = value; }}public Boss(string name){this.name = name;}public abstract bool PassRequest(Request request); } class PM:Boss {public PM(string name): base(name){ }public override bool PassRequest(Request request){int day = request.Day;string reason = request.Reason;if (day <= 0.5){return true;}return Successor.PassRequest(request);} } class HR:Boss {public HR(string name): base(name){ }public override bool PassRequest(Request request){int day = request.Day;string reason = request.Reason;if (day > 0.5&&day<=2){return true;}return Successor.PassRequest(request);} } class Manager : Boss {public Manager(string name): base(name){ }public override bool PassRequest(Request request){int day = request.Day;string reason = request.Reason;if (reason.Equals("正當理由")){return true;}return false;} }那么我們調用的時候就很簡單了!
static void Main(string[] args) {Request request = new Request(3, "非正當理由");Boss pm = new PM("pm");Boss hr = new HR("hr");Boss manager = new Manager("manager");pm.Successor = hr;hr.Successor = manager;bool pass = pm.PassRequest(request);Console.Write(pass); }五. 靈活在哪?
讓我們來看下職責鏈究竟靈活在哪?
1. 改變內部的傳遞規則。
在內部,項目經理完全可以跳過人事部到那一關直接找到總經理。
每個人都可以去動態地指定他的繼任者。
2. 可以從職責鏈任何一關開始。
如果項目經理不在,那么完全可以寫這樣的代碼:
static void Main(string[] args) {Request request = new Request(3, "非正當理由");Boss pm = new PM("pm");Boss hr = new HR("hr");Boss manager = new Manager("manager");pm.Successor = hr;hr.Successor = manager;//bool pass = pm.PassRequest(request);bool pass = hr.PassRequest(request);Console.Write(pass); }3. 我們來比較一下,用職責鏈和不用職責鏈的區別:
這是不用職責鏈我們的結構,我們需要和公司中的每一個層級都發生耦合關系。
如果反映在代碼上即使我們需要在一個類中去寫上很多丑陋的if….else語句。
如果用了職責鏈,相當于我們面對的是一個黑箱,我們只需要認識其中的一個部門,然后讓黑箱內部去負責傳遞就好了。
六. 職責鏈 != 鏈表
很多人都愿意把職責鏈和鏈表混為一談,確實,從字面意思上理解,鏈,鏈表,很像??墒撬麄円粯用?#xff1f;
他們區別在哪里:
讓我們看一個鏈表的典型結構:
讓我們來看一下鏈表的典型特征:
1. 鏈表是一個鏈狀結構,每個節點有一個next屬性去指向他的下一節點。
2. 鏈表有一個Header節點,然后用戶每次必須通過頭節點,然后去遍歷尋找每一個節點。
3. 鏈表遍歷操作的復雜度是O(n),但是插入和刪除指定節點的復雜度是常數級。
讓我們來著重看這第二點:
我們來想想在文章開始時我們畫出的那個鏈,一個鏈,我們可以從頭將他拿起,也可以從中間將他拿起:
也就是說我們用戶可以去訪問節點中的任何一個節點作為開始節點,這就是鏈表與職責鏈不同的地方。
七. 職責鏈的擴展——樹狀鏈結構
職責鏈中,我們之前看到的都是一些單鏈結構,但是其實在很多情況下,每一個節點都對應著很多其他的部分。
?
那么這樣,我們的每一個節點都可以使用一個List來維護他節點的下一節點,甚至可以用組合模式來分別設計每一節點。
八. 由法律想到——職責鏈的兜底條款
仔細想想法律條文,尤其是刑法,經常可以看到這樣的條文:
1. 如果*********,則處以拘役處分。
2. 如果*********,則處以有期徒刑一年到十年。
3. 如果*********,則處以有期徒刑十年以上。
4. 如果*********,則**********。
5. 如果以上條件皆不滿足,則*****************。
其實最后一條就叫做法律的兜底條款。這給了法官很大的自由裁量權,在一定程度上也降低了犯罪分子鉆法律空子的可能性。
在我們的職責鏈中,如果不存在這樣的兜底條款,那么用戶如果不從首節點開始訪問,那么就很可能出現異常的情況。于是我們應該為職責鏈設置一個默認的條款:
這樣的話,任何一個處理無論如何訪問,都能得到一個正常的處理。
九. 職責鏈的缺點
讓我們繼續回到上面的例子,我們發現,其實當請假時間超過2天的時候,PM和HR其實沒有做任何的事情,而只是做了一個傳遞工作。
而傳遞工作之后,他們就成了垃圾對象。
也就是說,他們在實際的處理中,并沒有發揮任何的作用。
那么當這個鏈結構比較長,比較復雜的話,會產生很多的內存垃圾對象。
這也就是職責鏈的最大缺點之所在。
十. 職責鏈的亂用
在和其他的人的討論中,我發現他們的觀點是:
只要一者傳一者,那么就要用職責鏈。在我們的項目中,他們這樣去用:
abstract class DBHelper { }interface IRequestHandler {IDBHelper ReturnHelper(string dbName); } class RequestHandler:IRequestHandler {private RequestHandler successor;public RequestHandler Successor{get { return successor; }set { successor = value; }}public abstract IDBHelper ReturnHelper(string dbName); }class SQLHelper : DBHelper { } class OracleHelper : DBHelper { } class DB2Helper : DBHelper { } class SQL : RequestHandler {public override IDBHelper ReturnHelper(string dbName){if (dbName.Equals("SQL Server")){return new SQLHelper();}return Successor.ReturnHelper(dbName);} } class Oracle : RequestHandler {public override IDBHelper ReturnHelper(string dbName){if (dbName.Equals("Oracle")){return new OracleHelper();}return Successor.ReturnHelper(dbName);} } class DB2 : RequestHandler {public override IDBHelper ReturnHelper(string dbName){if (dbName.Equals("DB2")){return new DB2Helper();}return new SQLHelper();} }這樣的話,每個類相當于只負責一個操作。
那么我們如何改進呢?第一,我們可以用一個工廠來實現。另外,我們可以用表驅動的方式來解決問題。
十一. 表驅動改進職責鏈
表驅動(Table driven),其實就是指用查表的方式來獲取值。
那么我們用標驅動法來改進上面的例子:
class HelperRequest {private Dictionary<String, DBHelper> dic = new Dictionary<string, DBHelper>();public void Add(string name,DBHelper helper){dic.Add(name, helper);}public DBHelper GetHelper(string name){DBHelper helper;bool temp = dic.TryGetValue(name, out helper);if (temp){return helper;}return null;} }我想一個沒有學過設計模式的人都會這樣寫的。一個學過設計模式很多年的人也會這樣寫的。
而怕的就是為了模式而模式,為了職責鏈而職責鏈了。
十二. 職責鏈在java script中的應用
我們想象這樣一種情況:
我們都知道,在ASP.NET 的 Webform模型中頁面是以控件樹的形式去組織的。那么我們用右鍵點擊其中的一個頁面,那么這個事件就會找離他最近的控件,如果不存在,那么就去找他的父控件,如此遞歸下去,直到找到為止。
這其實就是一種職責鏈的體現!
十三. 深析職責鏈的使用
職責鏈模式不能亂用,否則非常容易變成因為模式而模式的反例。
下面是我歸納出來的一些關于職責鏈方面的使用規則,只是個人的意見,還希望大家指教。
1, 如果存在N對N,或者是一般的常規線性關系,那么我們完全可以用表驅動來取代職責鏈。
2, 對象本身要經過什么處理是通過每個鏈上元素通過運行態來決定的,決定的因素是取決于對象的屬性或者一些其他方面的策略。
3, 用戶無論是從哪一個節點作為他的請求頭節點,最終用戶都可以得到一個請求的反饋。
4, 應怪怪建議,補充同級的處理!職責鏈并非是嚴格的上下級的傳遞,其中也包括同級的傳遞,職責鏈一樣可以在同級之間做傳遞。
例如,繼續用我們上面請假的那個做例子,也許我們公司有兩個HR,事實上也是這樣的,我們把前臺“MM”也美稱為人力資源部:
static void Main(string[] args) {Request request = new Request(3, "非正當理由");Boss pm = new PM("pm");Boss hr1 = new HR("Real HR");Boss hr2 = new HR("QiantaiMM");Boss manager = new Manager("manager");pm.Successor = hr1;hr1.Successor = hr2;hr2.Successor = manager;bool pass = pm.PassRequest(request);Console.Write(pass); }其實這樣也未嘗不可。有人也許會說,那么這樣的同樣一個類的兩個對象又有什么意義呢?
那么我們不妨去試著這樣改造這個HR的類。
enum HRType {RealHR,Qiantai } class HR:Boss {private HRType type;public HR(string name,HRType type): base(name){this.type = type;}public override bool PassRequest(Request request){int day = request.Day;if (day>=0.5&&day<2){switch (type){ case HRType.RealHR://扣工資return true;break;case HRType.Qiantai://不扣工資return true;break;}}return Successor.PassRequest(request);} }這樣,因為前臺MM容易說話,很可能他就不去扣你的工資,如果你去先找的HR,那么你這天的工資就報銷了。
同理,我們一樣可以讓他們的職責細化,比如說Real Hr負責0.5天到1天的,而Qiantai去負責1天到2天的,也未嘗不可。
總之,職責鏈并非是單一的上下級的傳遞,一樣可以實現同級的傳遞。
十四. 職責鏈總結
職責鏈是使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關系。將這個對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理他為止。
今天就寫到這了,希望大家多多指教。
轉載于:https://www.cnblogs.com/kym/archive/2009/04/06/1430078.html
總結
以上是生活随笔為你收集整理的重温设计模式(三)——职责链模式(chain of responsibility)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c#文本框只能填入数字和字母
- 下一篇: 母版页和相对路径