学习设计模式第十六 - 职责链模式
示例代码来自DoFactory。
概述
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。
意图
避免请求的发送者和接收者之间的耦合关系,并让请求有机会得到处理。
UML
图1 职责链模式UML图
参与者
这个模式涉及的类或对象:
-
Handler
-
定义处理请求的接口
-
(可选的)实现继承者链接
-
ConcreteHandler
-
处理其负责的请求
-
可以访问其后继者
-
如果其可以处理请求,则处理;否则将请求传递给其后继者
-
Client
-
初始化请求,将其给处理链上一个ConcreteHandler对象
适用性
面向对象设计的一个重要目标就是通过限制对象间依赖并保持其间关系特定且最小来保持对象间松耦合。相较于存在紧耦合对象的系统,松耦合对象的优势在于其易于维护且易于更改(如,硬编码来引用指定的类)。
职责链模式将处理对象放置在一个处理链中使客户端不用知道集合中哪一个对象可以处理请求从而提供了一种构建松耦合对象集合的方法。这种模式需要给搜索一个可以处理请求的对象的过程定义规则。搜索通常根据应用程序领域的特定需求来建模。注意职责链不常用于商业应用开发中。
-
有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。这个搜索
-
你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
-
可处理一个请求的对象集合应被动态指定。
DoFactory GoF代码
这个例子演示了职责链模式中几个关联在一起的对象被用来响应一个可处理请求或将其传递给链中下一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | // Chain of Responsibility pattern // Structural example using System; namespace DoFactory.GangOfFour.Chain.Structural { // MainApp test application class MainApp { static void Main() { // Setup Chain of Responsibility Handler h1 = new ConcreteHandler1(); Handler h2 = new ConcreteHandler2(); Handler h3 = new ConcreteHandler3(); h1.SetSuccessor(h2); h2.SetSuccessor(h3); // Generate and process request int [] requests = { 2, 5, 14, 22, 18, 3, 27, 20 }; foreach ( int request in requests) { h1.HandleRequest(request); } // Wait for user Console.ReadKey(); } } // "Handler" abstract class Handler { protected Handler successor; public void SetSuccessor(Handler successor) { this .successor = successor; } public abstract void HandleRequest( int request); } // "ConcreteHandler1" class ConcreteHandler1 : Handler { public override void HandleRequest( int request) { if (request >= 0 && request < 10) { Console.WriteLine( "{0} handled request {1}" , this .GetType().Name, request); } else if (successor != null ) { successor.HandleRequest(request); } } } // "ConcreteHandler2" class ConcreteHandler2 : Handler { public override void HandleRequest( int request) { if (request >= 10 && request < 20) { Console.WriteLine( "{0} handled request {1}" , this .GetType().Name, request); } else if (successor != null ) { successor.HandleRequest(request); } } } // "ConcreteHandler3" class ConcreteHandler3 : Handler { public override void HandleRequest( int request) { if (request >= 20 && request < 30) { Console.WriteLine( "{0} handled request {1}" , this .GetType().Name, request); } else if (successor != null ) { successor.HandleRequest(request); } } } } |
这个例子演示了几个关联在一起的经理和主管负责响应一个采购请求或将其传递给其上司。有一个规则定义每一个职位可以批准哪些订单。
例子中涉及到的类与职责链模式中标准的类对应关系如下:
-
Handler – Approver
-
ConcreteHandler – Director, VicePresident, President
-
Client - ChainApp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | // Chain of Responsibility pattern // Real World example using System; namespace DoFactory.GangOfFour.Chain.RealWorld { // MainApp test application class MainApp { static void Main() { // Setup Chain of Responsibility Approver larry = new Director(); Approver sam = new VicePresident(); Approver tammy = new President(); larry.SetSuccessor(sam); sam.SetSuccessor(tammy); // Generate and process purchase requests Purchase p = new Purchase(2034, 350.00, "Supplies" ); larry.ProcessRequest(p); p = new Purchase(2035, 32590.10, "Project X" ); larry.ProcessRequest(p); p = new Purchase(2036, 122100.00, "Project Y" ); larry.ProcessRequest(p); // Wait for user Console.ReadKey(); } } // "Handler" abstract class Approver { protected Approver successor; public void SetSuccessor(Approver successor) { this .successor = successor; } public abstract void ProcessRequest(Purchase purchase); } // "ConcreteHandler" class Director : Approver { public override void ProcessRequest(Purchase purchase) { if (purchase.Amount < 10000.0) { Console.WriteLine( "{0} approved request# {1}" , this .GetType().Name, purchase.Number); } else if (successor != null ) { successor.ProcessRequest(purchase); } } } // "ConcreteHandler" class VicePresident : Approver { public override void ProcessRequest(Purchase purchase) { if (purchase.Amount < 25000.0) { Console.WriteLine( "{0} approved request# {1}" , this .GetType().Name, purchase.Number); } else if (successor != null ) { successor.ProcessRequest(purchase); } } } // "ConcreteHandler" class President : Approver { public override void ProcessRequest(Purchase purchase) { if (purchase.Amount < 100000.0) { Console.WriteLine( "{0} approved request# {1}" , this .GetType().Name, purchase.Number); } else { Console.WriteLine( "Request# {0} requires an executive meeting!" , purchase.Number); } } } // Request details class Purchase { private int _number; private double _amount; private string _purpose; // Constructor public Purchase( int number, double amount, string purpose) { this ._number = number; this ._amount = amount; this ._purpose = purpose; } // Gets or sets purchase number public int Number { get { return _number; } set { _number = value; } } // Gets or sets purchase amount public double Amount { get { return _amount; } set { _amount = value; } } // Gets or sets purchase purpose public string Purpose { get { return _purpose; } set { _purpose = value; } } } } |
.NET优化版代码实现了与上一个例子相同的功能,并更多的采用了.NET内置特性。形成link的Successor通过属性来实现,从而简化了代码。此外,这个示例采用了时间驱动模型,涉及事件、委托和自定义事件参数。委托实现中使用了泛型,这样事件处理函数是类型安全的,其不限制object类型的事件触发者的类型而是限制适用于事件参数的类型 – 在示例代码中是类型Approver的事件处理程序的委托的第一个类型参数)。Purchase等其它类使用了.NET 3.0的自动属性和对象初始化器。
职责链模式在Windows事件模式中经常使用,如一个UI控件即可以处理一个事件(例如一个鼠标事件)或将其传给事件链中下一个控件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | // Chain of Responsibility pattern // .NET Optimized example using System; namespace DoFactory.GangOfFour.Chain.NETOptimized { class MainApp { static void Main() { // Setup Chain of Responsibility Approver larry = new Director(); Approver sam = new VicePresident(); Approver tammy = new President(); larry.Successor = sam; sam.Successor = tammy; // Generate and process purchase requests var purchase = new Purchase { Number = 2034, Amount = 350.00, Purpose = "Supplies" }; larry.ProcessRequest(purchase); purchase = new Purchase { Number = 2035, Amount = 32590.10, Purpose = "Project X" }; larry.ProcessRequest(purchase); purchase = new Purchase { Number = 2036, Amount = 122100.00, Purpose = "Project Y" }; larry.ProcessRequest(purchase); // Wait for user Console.ReadKey(); } } // Purchase event argument holds purchase info public class PurchaseEventArgs : EventArgs { internal Purchase Purchase { get ; set ; } } // "Handler" abstract class Approver { // Purchase event public EventHandler<PurchaseEventArgs> Purchase; // Purchase event handler public abstract void PurchaseHandler( object sender, PurchaseEventArgs e); // Constructor public Approver() { Purchase += PurchaseHandler; } public void ProcessRequest(Purchase purchase) { OnPurchase( new PurchaseEventArgs { Purchase = purchase }); } // Invoke the Purchase event public virtual void OnPurchase(PurchaseEventArgs e) { if (Purchase != null ) { Purchase( this , e); } } // Sets or gets the next approver public Approver Successor { get ; set ; } } // "ConcreteHandler" class Director : Approver { public override void PurchaseHandler( object sender, PurchaseEventArgs e) { if (e.Purchase.Amount < 10000.0) { Console.WriteLine( "{0} approved request# {1}" , this .GetType().Name, e.Purchase.Number); } else if (Successor != null ) { Successor.PurchaseHandler( this , e); } } } // "ConcreteHandler" class VicePresident : Approver { public override void PurchaseHandler( object sender, PurchaseEventArgs e) { if (e.Purchase.Amount < 25000.0) { Console.WriteLine( "{0} approved request# {1}" , this .GetType().Name, e.Purchase.Number); } else if (Successor != null ) { Successor.PurchaseHandler( this , e); } } } // "ConcreteHandler" class President : Approver { public override void PurchaseHandler( object sender, PurchaseEventArgs e) { if (e.Purchase.Amount < 100000.0) { Console.WriteLine( "{0} approved request# {1}" , sender.GetType().Name, e.Purchase.Number); } else if (Successor != null ) { Successor.PurchaseHandler( this , e); } else { Console.WriteLine( "Request# {0} requires an executive meeting!" , e.Purchase.Number); } } } // "request details" class Purchase { public double Amount { get ; set ; } public string Purpose { get ; set ; } public int Number { get ; set ; } } } |
职责链模式解说
在职责链模式中一个职责节点的有两点主要任务:
-
需要了解其后继者
-
在处理具体请求时判断是可以自行处理还是转给后继者去处理。
通过将处理的判断分配到每个具体的职责节点,我们可以避免将大量分支判断集中在一起。这也是解耦合的一种方式。
.NET中的指责链模式
在.NET中,你可以在Windows事件模型中指定一个职责链,在这个模型中每一个UI控件都可以决定是处理事件还是让其转向事件链中下一个控件。
你可能也会遇到这样的事件链实现,链中的对象在发送者到接收者之间处理一个消息,在消息由发送者到接收者传输过程中每个对象对消息进行一些处理。这与GoF的定义有些许不同,后者只有链条中的一个对象来处理来处理请求。.NET Framework中.NET Remoting实现了这种"逐步链条模式",.NET Remoting中一个消息在客户端与服务器之间传递时经过一个或多个所谓的消息槽。链中的每一个消息槽都有一个到链中下一个槽的引用。消息槽实现了IMessageSink接口且其中一个属性为NextSink。
效果及实现要点
职责链模式最关键的是当客户提交一个请求时,请求是沿链传递直至有一个ConcreteHandler对象负责处理它。这样接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可简化对象的相互连接,它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。这样大大降低了耦合度。另外由于是客户端定义链的结构,这样我们可以随时地增加或修改处理一个请求的结构,如我们可以选择跳过一个具体职责,直接将请求传递到更后续的职责来处理。
但灵活性的背后,一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得到不到处理。就像一封信由于目的地不对,最终无法被投递。
总结
职责链模式通过将消息处理器在实现相同契约的情况下独立出来,实现了消息发送与处理的解耦合,并且让消息处理有序,帮着每个消息都能正确的得到处理。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步