65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (20投票s)

2011年11月14日

CPOL

4分钟阅读

viewsIcon

58764

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

image

引言

本文将介绍如何实现责任链设计模式,以及如何对此进行一些可能的扩展。如果您是设计模式的新手,我建议您先阅读 实践应用设计模式 – 思维过程

回到责任链——如果您需要将多个处理器链接起来处理传入的请求或命令,那么最好使用责任链。

一个典型的例子是你的女朋友向你提出请求——如果她让你“和她一起去参加她最好的朋友的单身派对?”,你*会*直接处理。但如果她让你“给我买辆保时捷”,你会说“亲爱的,对不起,我没钱。你最好问你爸爸,我帮你打给他。”——也就是说,你将请求转交给下一个处理器,在这种情况下是你的女朋友的父亲。总而言之,在上面的例子中,你的女朋友是发出请求的客户,你和你的未来岳父是处理/批准她请求的处理器/批准者。如果你无法处理,你就将责任转交给链中的下一个处理器/批准者。

最小示例

为了考虑一个更正式的例子,假设你有一个银行系统,并且你想实现某种贷款审批。客户可能会申请贷款,如果金额低于特定金额,出纳员可以直接批准。如果金额高于指定金额,他可能会将请求转交给他的经理进行审批。

因此,您可以使用责任链实现将请求/命令交给正确的批准者。例如,考虑上述银行账户场景的实现。我们的业务规则是,出纳员可以批准金额低于 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();
        }
    }
}

执行后您会看到这个

image

因此,您可以观察到,在上例中,来自不同客户的贷款请求被传递给出纳员,出纳员在其 approve 方法中,如果金额高于他能批准的限额,则将请求传递给他的后继者(即经理)。如您所见,实现非常简洁。

image

我们有一个抽象请求处理器实现 IRequestHandler 和两个具体的请求处理器 CashierManager。每个请求处理器都可以持有对后继者的引用。您可以看到我们将 Cashier 的后继者设置为 Manager,因此如果请求的金额超出限制,cashier 可能会将其传递给 manager 进行审批。

动态注入批准者

现在,让我们退一步,思考如何以一种可扩展的方式实现审批流程?目前,我们的流程有两个批准者,cashiermanagermanager 可以批准高达 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 – 一个具体的请求
  • CashierManagerGeneralManager – 具体请求处理器

您可能会注意到,现在我们可以使用元数据链接处理器。例如,在导出 Manager 时,您可以轻松地指定 ManagerCashier 的后继者,以批准请求。同样,您可以将 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();
    }
}

您将获得这个。您可以看到请求被分派到正确的处理器。

image

image

image

image

好了,祝您编码愉快,并实现良好的解耦。

历史

  • 2011 年 11 月 14 日:初始发布
© . All rights reserved.