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

简介:

如何在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块来解决当前问题。

Copy
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客户始终由主管来处理), 这种逻辑将很难管理,并且很容易失控。
使用责任链模式编码#
下面让我们重写一些这部分代码。与之前不同,这里我们创建一些“员工”对象,里面封装了他们的处理逻辑。这里最重要的是,我们需要给每个员工对象指定一个直属上级,以便当他们处理不了当前请求的时候,可以将请求传递给直属上级。

Copy
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("银行经理提取的金额");
}

}
我们可以通过指定上级的方式创建出责任链。这看起来很像一个组织结构图。

Copy
var bankManager = new BankManager();
var bankSupervisor = new Supervisor { LineManager = bankManager };
var frontLineStaff = new Teller { LineManager = bankSupervisor };
这里我们可以创建一个BankAccount类,并将取款方法转换为由前台员工处理。

Copy
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++时代的事情了,那时候这通常是封装变量的首选方法。

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

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

Copy
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抽象类

Copy
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方法处理请求,否则就会尝试将请求传递给上级角色。

我们只需要像以下代码这样,更改创建员工链的方式即可:

Copy
var bankManager = new BankManager();

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

var frontLineStaff = new Teller();
frontLineStaff.SetLineManager(bankSupervisor);
这里我需要再次重申,我并不喜欢使用SetXXX这种方法,但是许多例子中都喜欢这么使用,所以我就把它加了进来。

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

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

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

出处:https://www.cnblogs.com/lwqlun/p/12846451.html

相关文章
|
4天前
|
消息中间件 前端开发 小程序
一个基于.NET Core构建的简单、跨平台、模块化的商城系统
今天大姚给大家分享一个基于.NET Core构建的简单、跨平台、模块化、完全开源免费(MIT License)的商城系统:Module Shop。
|
4天前
|
算法 C# 数据库
【干货】一份10万字免费的C#/.NET/.NET Core面试宝典
C#/.NET/.NET Core相关技术常见面试题汇总,不仅仅为了面试而学习,更多的是查漏补缺、扩充知识面和大家共同学习进步。该知识库主要由自己平时学习实践总结、网上优秀文章资料收集(这一部分会标注来源)和社区小伙伴提供三部分组成。该份基础面试宝典完全免费,发布两年来收获了广大.NET小伙伴的好评,我会持续更新和改进,欢迎关注我的公众号【追逐时光者】第一时间获取最新更新的面试题内容。
|
4天前
|
数据可视化 网络协议 C#
C#/.NET/.NET Core优秀项目和框架2024年3月简报
公众号每月定期推广和分享的C#/.NET/.NET Core优秀项目和框架(每周至少会推荐两个优秀的项目和框架当然节假日除外),公众号推文中有项目和框架的介绍、功能特点、使用方式以及部分功能截图等(打不开或者打开GitHub很慢的同学可以优先查看公众号推文,文末一定会附带项目和框架源码地址)。注意:排名不分先后,都是十分优秀的开源项目和框架,每周定期更新分享(欢迎关注公众号:追逐时光者,第一时间获取每周精选分享资讯🔔)。
|
4天前
|
SQL 数据库 C#
C# .NET面试系列十一:数据库SQL查询(附建表语句)
#### 第1题 用一条 SQL 语句 查询出每门课都大于80 分的学生姓名 建表语句: ```sql create table tableA ( name varchar(10), kecheng varchar(10), fenshu int(11) ) DEFAULT CHARSET = 'utf8'; ``` 插入数据 ```sql insert into tableA values ('张三', '语文', 81); insert into tableA values ('张三', '数学', 75); insert into tableA values ('李四',
76 2
C# .NET面试系列十一:数据库SQL查询(附建表语句)
|
4天前
|
开发框架 算法 搜索推荐
C# .NET面试系列九:常见的算法
#### 1. 求质数 ```c# // 判断一个数是否为质数的方法 public static bool IsPrime(int number) { if (number < 2) { return false; } for (int i = 2; i <= Math.Sqrt(number); i++) { if (number % i == 0) { return false; } } return true; } class Progr
65 1
|
4天前
|
开发框架 .NET 中间件
C#/.NET快速上手学习资料集(让现在的自己不再迷茫)
C#/.NET快速上手学习资料集(让现在的自己不再迷茫)
|
4天前
|
XML 开发框架 .NET
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
C#/ASP.NET应用程序配置文件app.config/web.config的增、删、改操作
15 1
|
4天前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
23 0
|
4天前
|
开发框架 前端开发 JavaScript
采用C#.Net +JavaScript 开发的云LIS系统源码 二级医院应用案例有演示
技术架构:Asp.NET CORE 3.1 MVC + SQLserver + Redis等 开发语言:C# 6.0、JavaScript 前端框架:JQuery、EasyUI、Bootstrap 后端框架:MVC、SQLSugar等 数 据 库:SQLserver 2012
23 0
|
4天前
|
Linux API iOS开发
.net core 优势
.NET Core 的优势:跨平台兼容(Windows, macOS, Linux)及容器支持,高性能,支持并行版本控制,丰富的新增API,以及开源。
28 4