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

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

什么是责任链?#

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

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

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

这里假设我们拥有一家银行,银行里面有3个级别的员工,分别是“柜员”、“主管”、“银行经理”。如果有人来取款,“柜员”只允许10,000美元以下的取款操作。如果金额超过10,000美元,那么它的请求将传递给“主管”。“主管”可以处理不超过100,000美元的请求,但前提是该账户在必须有身份证ID。如果没有身份证ID,则当前请求必须被拒绝。如果取款金额超过100,000美元,则当前请求可以转交给“银行经理”,“银行经理”可以批准任何取款金额,因为如果有人取超过100,000美元的金额,他们就是VIP, 我们不在乎VIP的身份证ID和其他规定。

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

一个糟糕的实现方式#

下面我们先使用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客户始终由主管来处理), 这种逻辑将很难管理,并且很容易失控。

使用责任链模式编码#

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

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

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

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

扩展我们的例子#

尽管我认为以上的例子已经能很好的说明这种模式,但是通常你会发现有些人会使用一个方法叫做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地狱中。

什么时候应该使用责任链模式?#

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

到此这篇关于详解如何在C#/.NET Core中使用责任链模式的文章就介绍到这了,更多相关C#/.NET Core 责任链模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

原文:Chain Of Responsbility Pattern In C#/.NET Core
作者:Wade
译者:Lamond Lu
出处:https://www.cnblogs.com/lwqlun/p/12846451.html

版权:本文采用「署名 4.0 国际」知识共享许可协议进行许可。

(0)

相关推荐

  • 快速学习C# 设计模式之职责链模式

    职责链模式简介及UML 职责链也叫责任链,他是一种行为型模式,它为请求创建了一个接收请求者对象的链,并将请求沿着这条链传递到目标对象去处理. 该模式最简单的实现方式就是运用里氏替换原则,对每个职责所持有的对象进行抽象,并使得每个职责对象都拥有共同的父类,通过对外提供出具有一般意义的接口. 范例 该范例,是我在对微服务中,服务发现的容错性进行处理的一种处理方案,考虑到服务发现过程中,如果注册中心宕机,那么可以使用本地文件存放的临时性信息,如果本地文件不存在,那么就直接用内容中存放的信息.在整个流程

  • c#设计模式之单例模式的实现方式

    场景描述 单例模式对于我们来说一点也不模式,是一个常见的名称,单例模式在程序中的实际效果就是:确保一个程序中只有一个实例,并提供一个全局访问点,节省系统资源 单例模式无论是在实际开发中还是在软件应用中比较常见,比如,windows系统的任务管理器.IIS的HttpApplication.实际项目中的日志组件等等 实现方式 单例模式为了实现一个实例,那么只有不把实例创建暴露出去,只通过类本身来创建实例,为了实现效果,需要定义一个私有构造函数 单例模式实现方式有:饿汉式.懒汉式.双重验证式.静态内部

  • 详解C++设计模式编程中责任链模式的应用

    职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 其思想很简单,比如考虑员工要求加薪.公司的管理者一共有三级,总经理.总监.经理,如果一个员工要求加薪,应该向主管的经理申请,如果加薪的数量在经理的职权内,那么经理可以直接批准,否则将申请上交给总监.总监的处理方式也一样,总经理可以处理所有请求.这就是典型的职责链模式,请求的处理形成了一条链,直到有一个对象处理请求.给出这个例子的UML图.

  • C#设计模式之Singleton模式

    前言 Singleton是二十三个设计模式中比较重要也比较经常使用的模式.但是这个模式虽然简单,实现起来也会有一些小坑,让我们一起来看看吧! 实现思路 首先我们看看这个设计模式的UML类图. 很清晰的可以看到,有三点是需要我们在实现这个模式的时候注意的地方. 私有化的构造器 全局唯一的静态实例 能够返回全局唯一静态实例的静态方法 其中,私有化构造器是防止外部用户创建新的实例而静态方法用于返回全局唯一的静态实例供用户使用.原理清楚了,接下来我们看看一些典型的实现方式和其中的暗坑. 实现方法 最简单

  • 深入了解c# 设计模式之简单工厂模式

    简单工厂模式,需要说明的是,它并不属于GOF 23种设计模式中的一种.但它却丰富了工厂模式家族,因为其简单的思想和使用方式,也有很广泛的使用 简单工厂模式又称为静态工厂模式,它用来确定创建哪一种对象的实例.这种模式应该说是最简单最实用的工厂模式了,它将外界创建对象的逻辑收集起来,做到了对外界隔离对象的创建逻辑的目的,使外面完全的成为了对象实例的使用者,明确了职责. 不过这种模式也有着非常明显是的缺点,工厂类中集中了所有对象实例的创建逻辑,造成了功能的高内聚:另外在扩展方面,如果需要添加新的类,就

  • C#设计模式之Facade外观模式解决天河城购物问题示例

    本文实例讲述了C#设计模式之Facade外观模式解决天河城购物问题.分享给大家供大家参考,具体如下: 一.理论定义 外观模式   把  分散的子系统,集合成一个系统,提供一站式服务. 二.应用举例 需求描述: 聂小倩 和 宁采臣是一对小富则安 的聊斋夫妻.住在比较偏远的小乡村. 今天,两人初次来到大城市广州,听说天河城提供一站式服务,不像小城市那样,买个东西  得  东奔西跑. 在一个地方,就可以买到 自己想要的衣服,电脑,鞋子,Iphone,还可以看大片, 吃冰淇淋,吃真功夫,买化妆品,珠宝首

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

    最近我有一个朋友在研究经典的"Gang Of Four"设计模式.他经常来询问我在实际业务应用中使用了哪些设计模式.单例模式.工厂模式.中介者模式 - 都是我之前使用过,甚至写过相关文章的模式.但是有一种模式是我还没有写过文章,即责任链模式. 什么是责任链?# 责任链模式(之前我经常称之为命令链模式)是一种允许以使用分层方式"处理"对象的模式.在维基百科中的经典定义是 在面向对象设计中,责任链模式是一种由命令对象源及其一系列处理对象组成的设计模式.每个处理对象包含了

  • 详解如何在ASP.NET Core中编写高效的控制器

    通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可以专注于测试业务逻辑和数据访问代码.瘦控制器的另一个优点是,它更容易维护控制器的多个版本. 这篇文章讨论了使控制器变胖的坏习惯,然后探索了使控制器变瘦和易于管理的方法.我列出编写控制器的最佳实践可能并不全面,但我已经讨论了最重要的一些,并在适当的情况下提供了相关的源代码.在接下来的几节中,我们将研究什么是胖控制

  • 详解如何在ASP.NET Core中使用IHttpClientFactory

    利用IHttpClientFactory可以无缝创建HttpClient实例,避免手动管理它们的生命周期. 当使用ASP.Net Core开发应用程序时,可能经常需要通过HttpClient调用WebAPI的方法以检查终结点是否正常工作.要实现这一点,通常需要实例化HttpClient并使用该实例来调用你的方法.但是直接使用HttpClient也有一些缺点,主要与手动管理实例的生命周期有关. 你可以使用IHttpClientFactory创建HttpClient来避免这些问题.IHttpClie

  • 详解如何在ASP.Net Core中实现健康检查

    健康检查 常用于判断一个应用程序能否对 request 请求进行响应,ASP.Net Core 2.2 中引入了 健康检查 中间件用于报告应用程序的健康状态. ASP.Net Core 中的 健康检查 落地做法是暴露一个可配置的 Http 端口,你可以使用 健康检查 去做一个最简单的活性检测,比如说:检查网络和系统的资源可用性,数据库资源是否可用,应用程序依赖的消息中间件或者 Azure cloud service 的可用性 等等,这篇文章我们就来讨论如何使用这个 健康检查中间件. 注册健康检查

  • 详解如何在ASP.NET Core中使用Route特性

    ASP.NET Core 中的 Route 中间件的职责在于将 request 匹配到各自 Route 处理程序上,Route 分两种:基于约定 和 基本特性 模式. 基于约定 模式的Route采用集中化的方式,而 基于特性 的方式允许你在 Action 或者 Controller 上单独定义,到底采用哪一种可以基于你自己的应用场景,本篇就来讨论如何使用 基于特性 模式. 创建 Controller 类 创建一个 DefaultController 类,新增如下代码. public class

  • 详解如何在ASP.NET Core中应用Entity Framework

    首先为大家提醒一点,.NET Core和经典.NET Framework的Library是不通用的,包括Entity Framework! 哪怎么办? 别急,微软为.NET Core发布了.NET Core版本的Entity Framework,具体配置方法与经典.NET Framework版本的稍有区别,下面的内容就为带领大家在ASP.NET Core中应用Entity Framework DB first. 注:目前部分工具处于Preview版本,正式版本可能会稍有区别. 前期准备: 1.推

  • 详解如何在ASP.NET Core中使用Redis

    Redis 是一个开源的内存中的数据结构存储系统,可以用作数据库.缓存和消息中间件.它支持多种类型的数据结构:字符串,哈希表,列表,集合,有序集等等. Redis 官方没有推出Windows版本,倒是由Microsoft Open Tech提供了Windows 64bit 版本支持. 如何在Windows机器上安装Redis=>下载安装文件Redis-x64-3.2.100.msi,安装完毕之后,打开service管理器,找到Redis服务,并将其启动.  前期准备: 1.推荐使用Visual

  • 详解如何在ASP.NET Core Web API中以三种方式返回数据

    在 ASP.NET Core 中有三种返回 数据 和 HTTP状态码 的方式,最简单的就是直接返回指定的类型实例,如下代码所示: [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { [HttpGet] public IEnumerable<WeatherForecast> Get() { var rng = new Random()

  • 详解如何在VS2019和VScode中配置C++调用python接口

    why 看到这个标题有些人说了,为什么好好的C++你非要调用python?人家明明是两种语言呀! 但是在实际应用中,有时候会用到C/C++调用python来更简单地去完成一些功能,不然人家python为什么有一个文件夹叫include,里边全是.h文件呢? VScode中配置环境 首先是在VScode中为C++调用python接口配置环境,这里假设你已经配置好了c++编程环境! 1.配置step1 用快捷键Ctrl+Shift+X打开Extensions 商店,输入python,install:

  • Java设计模式之责任链模式的概念、实现以及netty中的责任链模式

    本文先介绍了责任链模式的概念及简单实现.再贴了netty中对责任链的实现.最后总结了一点点思考. 1.概念相关 1.1.概念 责任链模式为请求创建了一个接收者对象的链,每个接收者都包含对另一个接收者的引用.如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,沿着这条链传递请求,直到有对象处理它为止. 1.2.解决了什么: 客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了. 1.3.场景: 1.有多个对象可以处理同一

随机推荐