责任链模式是一种行为型模式,允许你将请求沿着处理者链进行发送。收到请求后,每个处理者均可对请求进行处理,或将其传递给链上的下个处理者。
假如你正在开发一个在线订购系统。你希望对系统访问进行限制,只允许认证用户创建订单。此外,拥有管理权限的用户也拥有所有订单的完全访问权限。
简单规划后,你会意识到这些检查必须依次进行。只要接收到包含用户凭据的请求,应用程序就可尝试对进入系统的用户进行认证。但如果由于用户凭据不正确而导致认证失败,那就没有必要进行后续检查了。
在接下来的几个月里,你实现了后续的几个检查步骤。
检查代码本来就已经混乱不堪,而每次新增功能都会使其更加臃肿。修改某个检查步骤有时会影响其他的检查步骤。最糟糕的是,当你希望复用这些检查步骤来保护其他系统组件时,你只能复制部分代码,因为这些组件只需部分而非全部的检查步骤。
系统会变得让人非常费解,而且其维护成本也会激增。你在艰难地和这些代码共处一段时间后,有一天终于决定对整个系统进行重构。
与许多其他行为设计模式一样,责任链会将特定行为转换为被称作处理者的独立对象。在上述示例中,每个检查步骤都可被抽取为仅有单个方法的类,并执行检查操作。请求及其数据则会被作为参数传递给该方法。
模式建议你将这些处理者连成一条链。链上的每个处理者都有一个成员变量来保存对于下一处理者的引用。除了处理请求外,处理者还负责沿着链传递请求。请求会在链上移动,直至所有处理者都有机会对其进行处理。
最重要的是:处理者可以决定不再沿着链传递请求,这可高效地取消所有后续处理步骤。
在我们的订购系统示例中,处理者会在进行请求处理工作后决定是否继续沿着链传递请求。如果请求中包含正确的数据,所有处理者都将执行自己的主要行为,无论该行为是身份验证还是数据缓存。
不过还有一种稍微不同的方式(也是更经典一种),那就是处理者接收到请求后自行决定是否能够对其进行处理。如果自己能够处理,处理者就不再继续传递请求。因此在这种情况下,每个请求要么最多有一个处理者对其进行处理,要么没有任何处理者对其进行处理。在处理图形用户界面元素栈中的事件时,这种方式非常常见。
例如,当用户点击按钮时,按钮产生的事件将沿着 GUI 元素链进行传递,最开始是按钮的容器(如窗体或面板),直至应用程序主窗口。链上第一个能处理该事件的元素会对其进行处理。此外,该例还有另一个值得我们关注的地方:它表明我们总能从对象树中抽取出链来。
所有处理者类均实现同一接口是关键所在。每个具体处理者仅关心下一个包含execute执行
方法的处理者。这样一来,你就可以在运行时使用不同的处理者来创建链,而无需将相关代码与处理者的具体类进行耦合。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134 | using System;using System.Collections.Generic;namespace RefactoringGuru.DesignPatterns.ChainOfResponsibility.Conceptual{ // The Handler interface declares a method for building the chain of // handlers. It also declares a method for executing a request. public interface IHandler { IHandler SetNext(IHandler handler); object Handle(object request); } // The default chaining behavior can be implemented inside a base handler // class. abstract class AbstractHandler : IHandler { private IHandler _nextHandler; public IHandler SetNext(IHandler handler) { this._nextHandler = handler; // Returning a handler from here will let us link handlers in a // convenient way like this: // monkey.SetNext(squirrel).SetNext(dog); return handler; } public virtual object Handle(object request) { if (this._nextHandler != null) { return this._nextHandler.Handle(request); } else { return null; } } } class MonkeyHandler : AbstractHandler { public override object Handle(object request) { if ((request as string) == "Banana") { return $"Monkey: I'll eat the {request.ToString()}.\n"; } else { return base.Handle(request); } } } class SquirrelHandler : AbstractHandler { public override object Handle(object request) { if (request.ToString() == "Nut") { return $"Squirrel: I'll eat the {request.ToString()}.\n"; } else { return base.Handle(request); } } } class DogHandler : AbstractHandler { public override object Handle(object request) { if (request.ToString() == "MeatBall") { return $"Dog: I'll eat the {request.ToString()}.\n"; } else { return base.Handle(request); } } } class Client { // The client code is usually suited to work with a single handler. In // most cases, it is not even aware that the handler is part of a chain. public static void ClientCode(AbstractHandler handler) { foreach (var food in new List<string> { "Nut", "Banana", "Cup of coffee" }) { Console.WriteLine($"Client: Who wants a {food}?"); var result = handler.Handle(food); if (result != null) { Console.Write($" {result}"); } else { Console.WriteLine($" {food} was left untouched."); } } } } class Program { static void Main(string[] args) { // The other part of the client code constructs the actual chain. var monkey = new MonkeyHandler(); var squirrel = new SquirrelHandler(); var dog = new DogHandler(); monkey.SetNext(squirrel).SetNext(dog); // The client should be able to send a request to any handler, not // just the first one in the chain. Console.WriteLine("Chain: Monkey > Squirrel > Dog\n"); Client.ClientCode(monkey); Console.WriteLine(); Console.WriteLine("Subchain: Squirrel > Dog\n"); Client.ClientCode(squirrel); } }} |
---|
执行结果:
1234567891011121314151617 | Chain: Monkey > Squirrel > DogClient: Who wants a Nut? Squirrel: I'll eat the Nut.Client: Who wants a Banana? Monkey: I'll eat the Banana.Client: Who wants a Cup of coffee? Cup of coffee was left untouched.Subchain: Squirrel > DogClient: Who wants a Nut? Squirrel: I'll eat the Nut.Client: Who wants a Banana? Banana was left untouched.Client: Who wants a Cup of coffee? Cup of coffee was left untouched. |
---|
参考原文:责任链设计模式