解耦的艺术:用责任链模式构建可插拔的文件处理流水线

📝 摘要

在日常开发中,我们经常需要处理复杂的业务流程(如文件处理、审批流)。传统的硬编码方式往往导致代码耦合度高,一旦业务逻辑变更就需要修改核心代码,违背了"开闭原则"。本文将通过一个具体的文件处理案例,展示如何利用责任链模式(Chain of Responsibility) 结合 Fluent API,构建一个高度解耦、可插拔的处理流水线。


1. 引言:痛点与思考 💡

在软件开发中,我们经常遇到需要按顺序处理多个步骤的场景。例如,我们需要对一个文件夹进行:

  1. 获取当前目录下的文件;
  2. 获取当前目录下的子文件夹;
  3. 递归遍历子文件夹中的所有文件;
  4. 最后显示结果。

如果使用传统的硬编码,这些步骤会被死死地写在一起。如果未来我们需要增加一个"文件压缩"步骤,或者删除"显示文件"步骤,我们就得修改核心逻辑,这不仅风险大,而且违背了面向对象设计的"开闭原则"。

为了解决这个问题,我决定引入责任链模式。它能将复杂的处理流程拆解为独立的处理器,配合现代 C# 的 Fluent API 风格,我们可以像搭积木一样构建业务流程,实现高度的解耦与灵活配置。

2. 核心设计思路 🛠️

我们的设计目标是实现流程定义流程执行的分离。核心组件包括:

  1. 上下文(Context):作为"快递盒",在各个处理器之间传递数据。
  2. 抽象处理器(BaseHandler):定义标准接口,持有下一个处理器的引用。
  3. 链头入口(HeadChain) :实现 With 方法,支持流式配置(Fluent API)。
  4. 具体处理器:实现具体的业务逻辑(如搜索文件、搜索文件夹等)。
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 具体处理器实现

我们根据业务需求编写具体的处理器。每个处理器只关心自己的逻辑,互不干扰。

  • 获取文件处理器

    cs 复制代码
    public class GetFilesHandler : BaseHandler
    {
        public override void Handling(FileContext? context)
        {
            ArgumentNullException.ThrowIfNull(context);
            context.Files = Directory.GetFiles(context.Folder ?? "").ToList();
        }
    }
  • 获取文件夹处理器

    cs 复制代码
    internal 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. 总结与思考 🌟

这种设计模式的核心价值在于**"分离"**。

  1. 高内聚低耦合:每个处理器只负责单一职责,修改一个处理器不会影响其他处理器。
  2. 灵活配置 :我们将流程的定义(在 Main 方法中通过 With 组装)与流程的执行(在 Handle 中遍历)完全分离。
  3. 开闭原则 :当未来我们需要增加一个'文件压缩'步骤,或者删除'显示文件'步骤时,我们不需要修改一行核心逻辑代码,只需要在 With 链中增删处理器即可。

这就是架构的灵活性!通过责任链模式,我们把复杂的逻辑变成了可插拔的"乐高积木",让代码更易于维护和扩展。

相关推荐
bugcome_com2 小时前
【C# 数组详解】Array 定义、初始化、遍历、内存原理与面试高频问题
后端·c#·asp.net
小码编匠2 小时前
WPF 如何在 MVVM模式下实现 DataGrid编辑功能
后端·c#·.net
游乐码3 小时前
c#扩展方法
开发语言·c#
qq_454245034 小时前
基于ECS的工作流编排框架
数据结构·c#
dLNbrcWzV18 小时前
迈达斯桥梁建模与分析:探索多样桥梁结构的奥秘
.net
qq_4542450319 小时前
BuildTemplateGraph 函数深度解析:动态节点图构建的架构设计与核心价值
数据结构·c#
qq_4542450320 小时前
SkeletonFlow:基于组合子逻辑与范畴论的数据流处理框架
数据结构·c#
游乐码20 小时前
c#静态类和静态构造函数
开发语言·c#
net3m331 天前
自动分工 现象时,一共有几种可能得权重组合变化,如何确保这些组合的扫描时的不发生组合爆炸
人工智能·c#·ai编程