【23种设计模式】职责链模式(Chain of Responsibility Pattern) .Net Core实现

文章目录

    • 简介
    • 需求
    • 一个不好的实现
    • 职责链模式代码
    • 扩展我们的示例
    • 来源

简介

顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

何时使用:在处理消息的时候以过滤很多道。

如何解决:拦截的类都实现统一接口。

关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

应用实例: 1、红楼梦中的"击鼓传花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。 3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。

缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。

使用场景: 1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

需求

让我们假设我有一个银行,在这个银行中我有三个级别的员工,一个银行出纳,一个监督员,一个银行主管。如果一个人进来要提取一些存款,银行出纳可以允许任何少于10000美元的提取,不会问任何问题。如果取款金额超过10000美元,它便会被传给监督员进行处理。监督员可以处理上至100,000美元的请求,但必须要这个账户在记录中具有ID。如果ID没有在记录中,那么无论如何请求便会被拒绝。如果请求金额达到了 100,000美元,其便会流转到银行主管。银行主管可以审批任何金额的提取即使ID并不在记录中,因为如果他们提取那个金额,他们便是VIP并且我们不会在乎洗钱规则。

这便是我们之前讨论的层级链,其中每个人都尝试处理这个请求,并且能够把请求传递给下一个处理人。如果我们采取这种实现并且将其映射为代码(以一种优雅的方式),这就是我们所称的职责链模式。但是在我们进行任何深入之前,让我们看看一个坏的解决此问题的方式。

一个不好的实现

让我们只是使用if/else来解决这整个问题

class BankAccount
{
    bool idOnRecord { get; set; }
 
    void WithdrawMoney(decimal amount)
    {
        // Handled by the teller.
        if(amount < 10000)
        {
            Console.WriteLine("Amount withdrawn by teller");
        }
        // Handled by supervisor
        else if (amount < 100000)
        {
            if(!idOnRecord)
            {
                throw new Exception("Account holder does not have ID on record.");
            }
 
            Console.WriteLine("Amount withdrawn by Supervisor");
        }
        else
        {
            Console.WriteLine("Amount withdrawn by Bank Manager");
        }
    }
}

看得出来我们的代码有一些问题:

  • 在这里添加额外层级的员工是很难管理的,其会导致if/else语句的混乱。
  • 在监督员级别检查ID的特殊逻辑是某种难以进行单元测试的东西,因为其首先需要传递一些其他的检查。
  • 当仅有的定义逻辑是当时提取的金额,我们可以在未来添加额外的检查(比如,VIP客户被如此标记并总是被监督员处理)。这个逻辑将会变得很难维护并很容易失控。

职责链模式代码

让我们重写下代码。我们创建了employee对象,其可以处理判断他们本身能否处理这个请求的逻辑。在那之上,让我们给他们一个Line Manager,这样他们便知道在必要的时候他们可以将请求往上传递。

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("Amount withdrawn by Teller");
    }
}
 
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("Account holder does not have ID on record.");
        }
 
        Console.WriteLine("Amount withdrawn by Supervisor");
    }
}
 
class BankManager : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }
 
    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        Console.WriteLine("Amount withdrawn by Bank Manager");
    }
}
We can then create the “chain” by creating the employees required along with their managers. Almost like creating an Org Chart.
 
var bankManager = new BankManager();
var bankSupervisor = new Supervisor { LineManager = bankManager };
var frontLineStaff = new Teller { LineManager = bankSupervisor };
We can then completely transform the BankAccount class Withdraw method to instead be handled by our front line staff member (The Teller).
 
class BankAccount
{
    public bool idOnRecord { get; set; }
 
    public void WithdrawMoney(IBankEmployee frontLineStaff, decimal amount)
    {
            frontLineStaff.HandleWithdrawRequest(this, amount);
    }
}

那么我们便可以通过创建所需的员工及他们的Line Manger 来创建这个链。就像我们在创建一个组织结构图:

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

这样我们可以完全将BankAccount类WithDraw方法被替代为由我们的前线员工来处理(银行出纳):

class BankAccount
{
    public bool idOnRecord { get; set; }
 
    public void WithdrawMoney(IBankEmployee frontLineStaff, decimal amount)
    {
            frontLineStaff.HandleWithdrawRequest(this, amount);
    }
}
  • 现在,当我们做了一个取款申请,总是由出。纳首先处理,如果其不能处理,其会将这个请求传递给它的Line
    Manager,那可能是任何人。可以看出来,这个模式的优雅之处是:后续的处理节点没必要知道为什么事情被传递给自己。一个监督员不需要知道满足什么样的条件出纳会将请求传递到上面。
  • 出纳没有必要知道在它之后的整个链条。他只会将请求传递给监督员,请求便会在哪儿被处理(或者,如果需要的话,进一步传递)。
  • 整个组织架构图可以通过引入新的员工类型被改变。举个例子,如果我创建一个出纳主管,其会处理10K~50K的金额请求然后将其传递给监督员。出纳对象将会保持不变,并且我只会将出纳对象的Line Manage改变为“出纳 主管”作为代替。
  • 任何我们写的单元测试都会集中于一个单独的员工。举个例子,当我们测试一个监督员时,我们没有必要去测试一个出纳的逻辑。

扩展我们的示例

我认为如上的示例是一个优秀的方式来演示职责链模式,通常你会发现人们使用一个叫做"SetNext"的方法。通常我认为这在C#中极不寻常,因为我们有属性Getters 和 Setters。使用“SetVariableName” 方法通常是来自于使用C++的日子,其是封装变量的更推荐的方式。

但是在那之前,另一个示例同样典型的使用了抽象类来尝试以及限制请求是如何传递的。我们如上示例的问题在于有许多重复的代码来传递请求到下一个处理逻辑。让我们对其进行一点清理。

有许多的代码裸漏在我的面前。我们想做的第一件事是创建一个抽象类,其允许我们以一个标准化的方式来处理取款请求。它应该检查条件,如果通过,执行取款操作,如果没有通过,他需要将取款请求传递给他的Line Manage。它看起来像是这样:

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);
}

接下来我们需要更改我们的emloyee类使其继承于这个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("Amount withdrawn by Teller");
    }
}
 
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("Account holder does not have ID on record.");
        }
 
        Console.WriteLine("Amount withdrawn by Supervisor");
    }
}
 
class BankManager : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        return true;
    }
 
    protected override void Withdraw(BankAccount account, decimal amount)
    {
        Console.WriteLine("Amount withdrawn by Bank Manager");
    }
}

注意在所有的情形中,来自于抽象类的public方法 “HandleWithdrawRequest”都会被调用。然后其调用子类的“CanHandleRequest”方法,其包含了我们关于此员工是否合适的判断逻辑,然后其调用其本都的“Withdraw”方法。否则会尝试另一个员工。

我们只需要改变我们如何创建员工链,就如同这样:

var bankManager = new BankManager();
 
var bankSupervisor = new Supervisor();
bankSupervisor.SetLineManager(bankManager);
 
var frontLineStaff = new Teller();
frontLineStaff.SetLineManager(bankSupervisor);

再一次,我宁愿不使用"SetX"方法,但是其是许许多多的示例使用的东西因此我认为我应该包括它。

也有些示例会将一个员工是否能够处理请求的判断逻辑放在实际的抽象类中。我个人倾向于不这么做,因为这意味着我们的处理逻辑不得不具有非常相似的逻辑。例如,现在所有人都在检查要提取的金额,但是如果我们有一个特定的处理程序在寻找特定的东西(比如VIP标志?),将该逻辑添加到某些处理程序而不是其他处理程序的抽象类中只会让我们回到If/Else地狱。

来源

责任链模式|菜鸟教程
【译】C#/.Net core中的职责链模式

你可能感兴趣的:(开发,#,WEB_C#,设计模式,.netcore,责任链模式)