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

📝 摘要

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

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

相关推荐
数据的世界0113 小时前
C#4.0权威指南第12章:接口
开发语言·c#
c#上位机16 小时前
C#读取保存图像踩坑之FileStream类
开发语言·c#
manyikaimen21 小时前
博派智能-运动控制技术-RTCP-五轴联动
c++·图像处理·qt·算法·计算机视觉·机器人·c#
武藤一雄1 天前
C# 异步回调与等待机制
前端·microsoft·设计模式·微软·c#·.netcore
乱蜂朝王2 天前
使用 C# 和 ONNX Runtime 部署 PaDiM 异常检测模型
开发语言·c#
JosieBook2 天前
【C#】VS中的 跨线程调试异常:CrossThreadMessagingException
开发语言·c#
追雨潮2 天前
BGE-M3 多语言向量模型实战:.NET C# 从原理到落地
开发语言·c#·.net
CheerWWW2 天前
GameFramework——Download篇
笔记·学习·unity·c#
格林威2 天前
ZeroMQ 在视觉系统中的应用
开发语言·人工智能·数码相机·机器学习·计算机视觉·c#·视觉检测