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

📝 摘要

在日常开发中,我们经常需要处理复杂的业务流程(如文件处理、审批流)。传统的硬编码方式往往导致代码耦合度高,一旦业务逻辑变更就需要修改核心代码,违背了"开闭原则"。本文将通过一个具体的文件处理案例,展示如何利用责任链模式(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 链中增删处理器即可。

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

相关推荐
落叶@Henry17 小时前
C# async 和await 的面试题
开发语言·c#
人工智能AI技术17 小时前
C#接入CodeBuddy CLI实战:在.NET后端集成多AI Provider的全流程拆解
人工智能·c#
地球驾驶员18 小时前
NX二次开发C#-----NXopen测量两个面的投影距离
c#
猹叉叉(学习版)18 小时前
【ASP.NET CORE】 9. 托管服务
数据库·笔记·后端·c#·asp.net·.netcore
bugcome_com1 天前
C# 事件(Event)详解及实战示例
c#
CSharp精选营1 天前
.NET命名之谜:它与C#纠缠20年的关系揭秘
c#·.net·dotnet·csharp
是五月吖1 天前
【C#】SOLID原则
c#
就是有点傻1 天前
如何使用简单的服务端去接收数据
c#
AlphaNil1 天前
.NET + AI 跨平台实战系列(三):云端多模态API实战——用GPT-4V让App看懂世界
人工智能·后端·.net·maui
人工智能AI技术1 天前
两会“人工智能+“风口已至:C#开发者用Semantic Kernel搭建企业级Agent的3个实战套路
人工智能·c#