📝 摘要
在日常开发中,我们经常需要处理复杂的业务流程(如文件处理、审批流)。传统的硬编码方式往往导致代码耦合度高,一旦业务逻辑变更就需要修改核心代码,违背了"开闭原则"。本文将通过一个具体的文件处理案例,展示如何利用责任链模式(Chain of Responsibility) 结合 Fluent API,构建一个高度解耦、可插拔的处理流水线。
1. 引言:痛点与思考 💡
在软件开发中,我们经常遇到需要按顺序处理多个步骤的场景。例如,我们需要对一个文件夹进行:
- 获取当前目录下的文件;
- 获取当前目录下的子文件夹;
- 递归遍历子文件夹中的所有文件;
- 最后显示结果。
如果使用传统的硬编码,这些步骤会被死死地写在一起。如果未来我们需要增加一个"文件压缩"步骤,或者删除"显示文件"步骤,我们就得修改核心逻辑,这不仅风险大,而且违背了面向对象设计的"开闭原则"。
为了解决这个问题,我决定引入责任链模式。它能将复杂的处理流程拆解为独立的处理器,配合现代 C# 的 Fluent API 风格,我们可以像搭积木一样构建业务流程,实现高度的解耦与灵活配置。
2. 核心设计思路 🛠️
我们的设计目标是实现流程定义 与流程执行的分离。核心组件包括:
- 上下文(Context):作为"快递盒",在各个处理器之间传递数据。
- 抽象处理器(BaseHandler):定义标准接口,持有下一个处理器的引用。
- 链头入口(HeadChain) :实现
With方法,支持流式配置(Fluent API)。 - 具体处理器:实现具体的业务逻辑(如搜索文件、搜索文件夹等)。
3. 代码实现详解 💻
3.1 定义上下文(FileContext)
首先,我们需要一个容器来承载在处理链中流转的数据。
cs
namespace ChainOfResponsibilityDemo.Models
{
public class FileContext
{
public string? Folder { get; set; }
public List<string>? Files { get; set; }
public List<string>? Folders { get; set; }
public FileContext()
{
Folders = new List<string>();
Files = new List<string>();
}
}
}
3.2 抽象处理器(BaseHandler)
这是所有具体处理器的基类,它定义了处理请求的标准流程,并持有下一个处理器的引用。
cs
namespace ChainOfResponsibilityDemo.Models
{
public abstract class BaseHandler
{
// 持有下一个处理器的引用
public BaseHandler? Next { get; set; }
/// <summary>
/// 处理请求的入口
/// </summary>
public virtual BaseHandler Handle(FileContext? context)
{
// 1. 执行当前处理器的逻辑
Handling(context);
// 2. 如果有下一个处理器,传递下去(递归调用)
return Next?.Handle(context) ?? this;
}
/// <summary>
/// 子类必须实现的具体处理逻辑
/// </summary>
protected abstract void Handling(FileContext? context);
}
}
3.3 链头入口(HeadChain)- 核心亮点
这是实现流式配置的关键。With 方法负责将各个处理器串联起来,形成一条链表。
cs
namespace ChainOfResponsibilityDemo.Models
{
public class HeadChain : BaseHandler
{
public HeadChain With(BaseHandler handler)
{
ArgumentNullException.ThrowIfNull(handler);
if (Next is null)
{
Next = handler; // 链为空,直接赋值
}
else
{
// 找到链尾(最后一个节点)
var current = Next;
while (current.Next is not null)
{
current = current.Next;
}
current.Next = handler; // 将新处理器挂载到尾部
}
return this; // 返回自身以支持链式调用
}
}
}
3.4 具体处理器实现
我们根据业务需求编写具体的处理器。每个处理器只关心自己的逻辑,互不干扰。
-
获取文件处理器
cspublic class GetFilesHandler : BaseHandler { public override void Handling(FileContext? context) { ArgumentNullException.ThrowIfNull(context); context.Files = Directory.GetFiles(context.Folder ?? "").ToList(); } }
-
获取文件夹处理器
csinternal class GetFoldersHandler : BaseHandler { public override void Handling(FileContext? context) { ArgumentNullException.ThrowIfNull(context); context.Folders = Directory.GetDirectories(context.Folder ?? "").ToList(); } }递归处理子文件夹处理器
cs
public class ProcessFoldersHandler : BaseHandler
{
public override void Handling(FileContext? context)
{
ArgumentNullException.ThrowIfNull(context);
var folders = context.Folders?.ToList();
Queue<string> foldersQueue = new(folders ?? []);
while (foldersQueue.Count > 0)
{
string currentFolder = foldersQueue.Dequeue();
// 将当前文件夹下的文件加入结果
foreach (var item in Directory.GetFiles(currentFolder))
{
context?.Files?.Add(item);
}
// 将子文件夹入队,等待处理
foreach (var subFolder in Directory.GetDirectories(currentFolder))
{
foldersQueue.Enqueue(subFolder);
}
}
}
}
结果显示处理器
*
cs
public class DisplayFilesHandler : BaseHandler
{
public override void Handling(FileContext? context)
{
ArgumentNullException.ThrowIfNull(context);
Console.WriteLine("=== 显示文件 ===");
context.Files?.ForEach(Console.WriteLine);
Console.WriteLine("\n=== 显示文件夹 ===");
context.Folders?.ForEach(Console.WriteLine);
}
}
运行与测试 🚀
在 Main 方法中,我们不再关心具体的执行细节,只需要关注**"我需要做什么"** 。通过 With 方法,我们可以随意组合处理步骤。
cs
static void Main(string[] args)
{
var fileContext = new FileContext() { Folder = @"D:\TestFolder" };
// 构建责任链:像搭积木一样配置流程
var chain = new HeadChain()
.With(new GetFilesHandler())
.With(new GetFoldersHandler())
.With(new ProcessFoldersHandler())
.With(new DisplayFilesHandler());
// 执行流程
chain.Handle(fileContext);
Console.ReadLine();
}
4. 总结与思考 🌟
这种设计模式的核心价值在于**"分离"**。
- 高内聚低耦合:每个处理器只负责单一职责,修改一个处理器不会影响其他处理器。
- 灵活配置 :我们将流程的定义(在
Main方法中通过With组装)与流程的执行(在Handle中遍历)完全分离。 - 开闭原则 :当未来我们需要增加一个'文件压缩'步骤,或者删除'显示文件'步骤时,我们不需要修改一行核心逻辑代码,只需要在
With链中增删处理器即可。
这就是架构的灵活性!通过责任链模式,我们把复杂的逻辑变成了可插拔的"乐高积木",让代码更易于维护和扩展。