如何在C#/.NET Core中使用责任链模式

原文:Chain Of Responsbility Pattern In C#/.NET Core
做者:Wade
译者:Lamond Luc#

最近我有一个朋友在研究经典的“Gang Of Four”设计模式。他常常来询问我在实际业务应用中使用了哪些设计模式。单例模式、工厂模式、中介者模式 - 都是我以前使用过,甚至写过相关文章的模式。可是有一种模式是我尚未写过文章,即责任链模式。设计模式

什么是责任链?

责任链模式(以前我常常称之为命令链模式)是一种容许以使用分层方式”处理“对象的模式。在维基百科中的经典定义是架构

在面向对象设计中,责任链模式是一种由命令对象源及其一系列处理对象组成的设计模式。每一个处理对象包含了它能够处理的命令对象的逻辑,其他的将传递给链中的下一个处理对象。固然,这里还存在一种将新的处理对象追加到链尾的机制。所以责任链是If..else if.. else if...else...endif的面向对象版本。其优势是能够在运行时动态从新排列或配置条件操做块。ide

也许你会觉着上面的概念描述过于抽象,不容易理解,那么下面让咱们来看一个真实生活中的例子。单元测试

这里假设咱们拥有一家银行,银行里面有3个级别的员工,分别是“柜员”、“主管”、“银行经理”。若是有人来取款,“柜员”只容许10,000美圆如下的取款操做。若是金额超过10,000美圆,那么它的请求将传递给“主管”。“主管”能够处理不超过100,000美圆的请求,但前提是该帐户在必须有身份证ID。若是没有身份证ID,则当前请求必须被拒绝。若是取款金额超过100,000美圆,则当前请求能够转交给“银行经理”,“银行经理”能够批准任何取款金额,由于若是有人取超过100,000美圆的金额,他们就是VIP, 咱们不在意VIP的身份证ID和其余规定。测试

这就是咱们前面讨论的分层“链”,每一个人都尝试处理当前请求,若是没有知足要求,就传递给下一个。若是咱们将这种场景转换成代码,就是咱们所说的责任链模式。可是在这以前,让咱们先来看一个糟糕的实现方法。this

一个糟糕的实现方式

下面咱们先使用If/Else块来解决当前问题。编码

class BankAccount
{
    bool idOnRecord { get; set; }

    void WithdrawMoney(decimal amount)
    {
        // 柜员处理
        if(amount < 10000)
        {
            Console.WriteLine("柜员提取的金额");
        } 
        // 主管处理
        else if (amount < 100000)
        {
            if(!idOnRecord)
            {
                throw new Exception("客户没有身份证ID");
            }

            Console.WriteLine("主管提取的金额");
        }
        else
        {
            Console.WriteLine("银行经理提取的金额");
        }
    }
}

以上这种实现方式有几个问题:设计

  • 添加一种新的员工级别会至关困难,由于IF/Else代码块看起来太乱了
  • “主管”检查身份证ID的逻辑在某种程度上很难进行单元测试,由于它必须首先经过其余的检查
  • 虽然如今咱们只定义了提款金额的逻辑,可是若是在未来咱们想要添加其余检查(例如:VIP客户始终由主管来处理), 这种逻辑将很难管理,而且很容易失控。

使用责任链模式编码

下面让咱们重写一些这部分代码。与以前不一样,这里咱们建立一些“员工”对象,里面封装了他们的处理逻辑。这里最重要的是,咱们须要给每一个员工对象指定一个直属上级,以便当他们处理不了当前请求的时候,能够将请求传递给直属上级。code

interface IBankEmployee
{
    IBankEmployee LineManager { get; }
    void HandleWithdrawRequest(BankAccount account, decimal amount);
}

class Teller : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if(amount > 10000)
        {
            LineManager.HandleWithdrawRequest(account, amount);
            return;
        }

        Console.WriteLine("柜员提取的金额");
    }
}

class Supervisor : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if (amount > 100000)
        {
            LineManager.HandleWithdrawRequest(account, amount);
            return;
        }

        if(!account.idOnRecord)
        {
            throw new Exception("客户没有身份证ID");
        }

        Console.WriteLine("主管提取的金额");
    }
}

class BankManager : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        Console.WriteLine("银行经理提取的金额");
    }
}

咱们能够经过指定上级的方式建立出责任链。这看起来很像一个组织结构图。

var bankManager = new BankManager();
var bankSupervisor = new Supervisor { LineManager = bankManager };
var frontLineStaff = new Teller { LineManager = bankSupervisor };

这里咱们能够建立一个BankAccount类,并将取款方法转换为由前台员工处理。

class BankAccount
{
    public bool idOnRecord { get; set; }

    public void WithdrawMoney(IBankEmployee frontLineStaff, decimal amount)
    {
         frontLineStaff.HandleWithdrawRequest(this, amount);
    }
}

如今,当咱们进行取款请求的时候,“柜员”老是第一个来处理,若是处理不了,它会自动将请求发给直属领导。这种模式的优雅之处有如下几点:

  • 链中的后续子项并不须要知道是哪一个子项将命令传递给它的。就像这里,“主管”不须要知道是为何下级“柜员”为何会把请求传递给他
  • "柜员"不须要知道整个链。他仅负责将请求传递给上级""主管"",指望请求能在上级“主管”那里被处理(当前也许还须要进一步的传递处理)便可
  • 当引入新员工类型的时候,整个组织架构图很容易变动。例如, 我建立了一个新的“柜员经理”角色,他能处理10,000-50,000美圆之间的提款请求,“柜员经理”的直属上级是“主管”。这里咱们并不须要对“主管”对象作任何的处理,只须要将“柜员”的直属上级改成“柜员经理”便可
  • 当编写单元测试的时候,咱们能够一次只关注一个雇员角色了。例如,在测试“主管”逻辑的时候,咱们就不须要测试“柜员”的逻辑了

扩展咱们的例子

尽管我认为以上的例子已经能很好的说明这种模式,可是一般你会发现有些人会使用一个方法叫作SetNext.通常来讲,我觉着这在C#中是很是罕见的,由于C#中咱们可使用属性获取器和设置器。使用SetVariableName方法一般都是C++时代的事情了,那时候这一般是封装变量的首选方法。

但这里最重要的是,其余示例一般使用抽象类来增强请求传递的方式。在前面代码中有一个问题是,将请求传递给下一个处理器的时候,编写了许多重复代码。那么就让咱们来整理一下代码。

这里咱们要作的第一件事情就是建立一个抽象类,这个抽象类使咱们可以经过标准化的方式处理提款请求。它应该定义一个检测条件,若是条件知足,就执行提款,反之,就将请求传递给直属上级。通过修改以后的代码以下:

interface IBankEmployee
{
    IBankEmployee LineManager { get; }
    void HandleWithdrawRequest(BankAccount account, decimal amount);
}

abstract class BankEmployee : IBankEmployee
{
    public IBankEmployee LineManager { get; private set; }

    public void SetLineManager(IBankEmployee lineManager)
    {
        this.LineManager = lineManager;
    }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if (CanHandleRequest(account, amount))
        {
            Withdraw(account, amount);
        } 
        else
        {
            LineManager.HandleWithdrawRequest(account, amount);
        }
    }

    abstract protected bool CanHandleRequest(BankAccount account, decimal amount);

    abstract protected void Withdraw(BankAccount account, decimal amount);
}

下一步,咱们须要修改全部的员工类,使其继承自BankEmployee抽象类

class Teller : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        if (amount > 10000)
        {
            return false;
        }
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        Console.WriteLine("柜员提取的金额");
    }
}

class Supervisor : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        if (amount > 100000)
        {
            return false;
        }
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        if (!account.idOnRecord)
        {
            throw new Exception("客户没有身份证ID");
        }

        Console.WriteLine("主管提取的金额");
    }
}

class BankManager : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        Console.WriteLine("银行经理提取的金额");
    }
}

这里请注意,在全部的场景中,都会调用抽象类中的HandleWithdrawRequest公共方法。 该方法会调用子类中定义的CanHandleRequest方法来检测当前角色是否知足处理请求的条件,若是知足,就调用子类中的Withdraw方法处理请求,不然就会尝试将请求传递给上级角色。

咱们只须要像如下代码这样,更改建立员工链的方式便可:

var bankManager = new BankManager();

var bankSupervisor = new Supervisor();
bankSupervisor.SetLineManager(bankManager);

var frontLineStaff = new Teller();
frontLineStaff.SetLineManager(bankSupervisor);

这里我须要再次重申,我并不喜欢使用SetXXX这种方法,可是许多例子中都喜欢这么使用,因此我就把它加了进来。

在一些例子中,也会将判断员工是否知足处理请求的条件放在抽象类中。我我的不喜欢这样作,由于这意味着全部的处理程序不得不使用类似的逻辑。例如,目前全部的检查都是基于提取金额的,可是若是咱们想要实现一个特殊的处理程序,它的条件和VIP标志有关,那么咱们将不得不又在抽象类中从新使用IF/Else, 这又将咱们带回到了IF/Else地狱中。

何时应该使用责任链模式?

这种模式最佳的使用场景是,你的业务上有一个逻辑上的处理链,这个处理链每次必须按照顺序运行。这里请注意,链分叉是这种模式的一个变体, 可是很快处理起来就会很是复杂。所以,当我对现实世界中“命令链”场景建模的时候,我一般会使用这种模式。这就是我以银行为例的缘由,由于它就是现实世界中能够用代码建模的“责任链”。

相关文章
相关标签/搜索