C# 中的责任链设计模式,使用托管可扩展性框架 (MEF)





5.00/5 (20投票s)
本文将介绍如何实现责任链设计模式,以及如何使用托管可扩展性框架 (MEF) 来扩展该模式。

引言
本文将介绍如何实现责任链设计模式,以及如何对此进行一些可能的扩展。如果您是设计模式的新手,我建议您先阅读 实践应用设计模式 – 思维过程。
回到责任链——如果您需要将多个处理器链接起来处理传入的请求或命令,那么最好使用责任链。
一个典型的例子是你的女朋友向你提出请求——如果她让你“和她一起去参加她最好的朋友的单身派对?”,你*会*直接处理。但如果她让你“给我买辆保时捷”,你会说“亲爱的,对不起,我没钱。你最好问你爸爸,我帮你打给他。”——也就是说,你将请求转交给下一个处理器,在这种情况下是你的女朋友的父亲。总而言之,在上面的例子中,你的女朋友是发出请求的客户,你和你的未来岳父是处理/批准她请求的处理器/批准者。如果你无法处理,你就将责任转交给链中的下一个处理器/批准者。
最小示例
为了考虑一个更正式的例子,假设你有一个银行系统,并且你想实现某种贷款审批。客户可能会申请贷款,如果金额低于特定金额,出纳员可以直接批准。如果金额高于指定金额,他可能会将请求转交给他的经理进行审批。
因此,您可以使用责任链实现将请求/命令交给正确的批准者。例如,考虑上述银行账户场景的实现。我们的业务规则是,出纳员可以批准金额低于 1000 美元的请求,否则应将审批移交给经理。经理可以批准金额低于 10,000 美元的请求。
我们有以下组件。
LoanRequest
– 一个具体的请求IRequestHandler
– 抽象请求处理器实现- Cashier 和 Manager 等具体处理器实现此接口
- 拥有指向后继者的引用,以传递请求
Program
– 主驱动程序
进入代码
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DesignPatternsCof
{
//Request
class LoanRequest
{
public string Customer { get; set; }
public decimal Amount { get; set; }
}
//Abstract Request Handler
interface IRequestHandler
{
string Name { get; set; }
void HandleRequest(LoanRequest req);
IRequestHandler Successor { get; set; }
}
//Just an extension method for the passing the request
static class RequestHandlerExtension
{
public static void TrySuccessor(this IRequestHandler current, LoanRequest req)
{
if (current.Successor != null)
{
Console.WriteLine("{0} Can't approve - Passing request to {1}",
current.Name, current.Successor.Name);
current.Successor.HandleRequest(req);
}
else
{
Console.WriteLine("Amount invalid, no approval given");
}
}
}
//Concrete Request Handler - Cashier
//Cashier can approve requests upto 1000$$
class Cashier : IRequestHandler
{
public string Name { get; set; }
public void HandleRequest(LoanRequest req)
{
Console.WriteLine("\n----\n{0} $$ Loan Requested by {1}",
req.Amount, req.Customer);
if (req.Amount<1000)
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount,req.Customer, this.Name);
else
this.TrySuccessor(req);
}
public IRequestHandler Successor { get; set; }
}
//Concrete Request Handler - Manager
//Manager can approve requests upto 10000$
class Manager : IRequestHandler
{
public string Name { get; set; }
public void HandleRequest(LoanRequest req)
{
if (req.Amount < 10000)
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.Name);
else
this.TrySuccessor(req);
}
public IRequestHandler Successor { get; set; }
}
//Main driver
class Program
{
static void Main(string[] args)
{
//Customers
var request1 = new LoanRequest() { Amount = 800, Customer = "Jimmy"};
var request2 = new LoanRequest() { Amount = 5000, Customer = "Ben"};
var request3 = new LoanRequest() {Amount = 200000, Customer = "Harry"};
//Approvers, chained together
var manager = new Manager() {Name = "Tom, Manager"};
var cashier = new Cashier(){ Name = "Job, Cachier", Successor = manager};
//All customers request cashier first to approve
cashier.HandleRequest(request1);
cashier.HandleRequest(request2);
cashier.HandleRequest(request3);
Console.ReadLine();
}
}
}
执行后您会看到这个
因此,您可以观察到,在上例中,来自不同客户的贷款请求被传递给出纳员,出纳员在其 approve
方法中,如果金额高于他能批准的限额,则将请求传递给他的后继者(即经理)。如您所见,实现非常简洁。
我们有一个抽象请求处理器实现 IRequestHandler
和两个具体的请求处理器 Cashier
和 Manager
。每个请求处理器都可以持有对后继者的引用。您可以看到我们将 Cashier
的后继者设置为 Manager
,因此如果请求的金额超出限制,cashier
可能会将其传递给 manager
进行审批。
动态注入批准者
现在,让我们退一步,思考如何以一种可扩展的方式实现审批流程?目前,我们的流程有两个批准者,cashier
和 manager
,manager
可以批准高达 10,000 的贷款。明天,银行可能会决定总经理可以批准 10,000 以上的贷款——您将如何处理?进行更改,重新编译整个应用程序,将其移交给 QA,启动全面的递归测试,然后将所有内容部署到生产环境?您可以利用一些可扩展性,让我们来看看如何为此利用 MEF(托管可扩展性框架)。
如果您不熟悉 MEF 的概念,我建议您阅读我关于 MEF 的入门文章。
让我们进行通用实现,利用 MEF 加载和组合处理器。我们对上述实现进行一些泛化,并引入一些更通用的契约和类。
IRequest
– 所有请求都应实现此契约。IRequestHandler
– 与之前相同。抽象请求处理器实现ExportHandlerAttribute
– 用于导出 MEF 部件的自定义属性IRequestHandlerMetadata
– 内部用于将后继者信息存储为类型RequestHandlerGateway
– 执行组合,并以链式方式将请求传递给后继者。
进入代码
//Abstract Request
public interface IRequest { }
//Abstract Request Handler
public interface IRequestHandler
{
bool HandleRequest(IRequest req);
IRequestHandler Successor { get; set; }
}
//A custom MEF Export attribute
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportHandlerAttribute : ExportAttribute
{
public Type SuccessorOf { get; set; }
public ExportHandlerAttribute()
: base(typeof(IRequestHandler))
{
}
public ExportHandlerAttribute(Type successorOf)
: base(typeof(IRequestHandler))
{
this.SuccessorOf = successorOf;
}
}
//The metadata to tie a handler to next successor
public interface IRequestHandlerMetadata
{
Type SuccessorOf { get; }
}
//A gateway which stitches together the handlers,
//to accept a request to chain through the handlers
//Note that this does the composition using MEF
public class RequestHandlerGateway
{
[ImportMany(typeof(IRequestHandler))]
public IEnumerable<Lazy<IRequestHandler,IRequestHandlerMetadata>>
Handlers { get; set; }
private IRequestHandler first = null;
public RequestHandlerGateway()
{
ComposeHandlers();
//Let us find and keep the first handler
//i.e, the handler which is not a successor of any other handlers
first = Handlers.First
(handler => handler.Metadata.SuccessorOf == null).Value;
}
//Compose the handlers
void ComposeHandlers()
{
//A catalog that can aggregate other catalogs
var aggrCatalog = new AggregateCatalog();
//An assembly catalog to load information about part from this assembly
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
aggrCatalog.Catalogs.Add(asmCatalog);
//Create a container
var container = new CompositionContainer(aggrCatalog);
//Composing the parts
container.ComposeParts(this);
}
//Try to handle the request, pass to successor if required
bool TryHandle(IRequestHandler handler, IRequest req)
{
var s =
Handlers.FirstOrDefault(
h => h.Metadata.SuccessorOf == handler.GetType());
if (handler.HandleRequest(req))
return true;
else if (s != null)
{
handler.Successor = s.Value;
return TryHandle(handler.Successor, req);
}
else
return false;
}
//Main gateway method for invoking the same from the driver
public bool HandleRequest(IRequest request)
{
return TryHandle(first,request);
}
}
太棒了。我们已经有了基础的东西,请妥善保管。现在,要实现责任链,您可以直接创建具体部件并导出它们。我们有以下具体部件
LoanRequest
– 一个具体的请求Cashier
、Manager
和GeneralManager
– 具体请求处理器
您可能会注意到,现在我们可以使用元数据链接处理器。例如,在导出 Manager 时,您可以轻松地指定 Manager
是 Cashier
的后继者,以批准请求。同样,您可以将 General Manager 指定为 Manager
的后继者。优点是,您可以将这些组件部署在松散耦合的管理器中,并在重新组合期间使用 MEF 的 DirectoryCatalog
来拾取它们。
//Concrete Request
public class LoanRequest : IRequest
{
public string Customer { get; set; }
public decimal Amount { get; set; }
}
//Concrete Request Handler - Cashier
//Cashier can approve requests upto 1000$$
[ExportHandler]
public class Cashier : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 1000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
//Concrete Request Handler - Manager
//Manager can approve requests upto 10000$
[ExportHandler(SuccessorOf = typeof(Cashier))]
public class Manager : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 10000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
//Concrete Request Handler - Manager
//Manager can approve requests upto 10000$
[ExportHandler(SuccessorOf = typeof(Manager))]
public class GeneralManager : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 100000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
//Main driver
class Program
{
static void Main(string[] args)
{
//Customers
Console.WriteLine("Enter Loan Amount:");
var amount = decimal.Parse(Console.ReadLine());
var req = new LoanRequest() {Amount = amount, Customer = "Ben"};
var gateway = new RequestHandlerGateway();
if (!gateway.HandleRequest(req))
Console.WriteLine("Oops, too high. Rejected");
Console.ReadLine();
}
}
您将获得这个。您可以看到请求被分派到正确的处理器。
好了,祝您编码愉快,并实现良好的解耦。
历史
- 2011 年 11 月 14 日:初始发布