You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

事件依赖注入:Worker类双事件实现与注入方案问询

改进Worker类实现,满足可测试、SRP与可扩展性要求

核心思路

现有代码的问题在于Worker类直接耦合事件触发逻辑,违反单一职责原则(SRP)且难以做无副作用测试。正确的做法是将事件对应的业务逻辑抽象为独立接口,通过依赖注入传入Worker,让Worker只专注于自身核心职责(执行DoStuff并通知处理逻辑),事件处理逻辑由专门的类负责。

具体实现步骤

1. 抽象事件处理接口

针对两个独立逻辑分别定义接口,每个接口只负责单一职责:

// 处理文件发现逻辑的接口
public interface IFileFoundHandler
{
    Task HandleFileFoundAsync(FileInfo file);
}

// 处理HTTP发送逻辑的接口
public interface ISendHttpHandler
{
    Task SendHttpAsync(FileInfo file);
}

2. 重构Worker类

移除直接的事件定义,通过构造函数注入上述接口,ExecuteAsync中调用接口方法:

public class Worker
{
    private readonly IFileFoundHandler _fileFoundHandler;
    private readonly ISendHttpHandler _sendHttpHandler;

    // 构造函数注入依赖,确保依赖不为空
    public Worker(IFileFoundHandler fileFoundHandler, ISendHttpHandler sendHttpHandler)
    {
        _fileFoundHandler = fileFoundHandler ?? throw new ArgumentNullException(nameof(fileFoundHandler));
        _sendHttpHandler = sendHttpHandler ?? throw new ArgumentNullException(nameof(sendHttpHandler));
    }

    public async Task ExecuteAsync()
    {
        var file = DoStuff(); // 假设DoStuff返回处理后的文件信息
        await _fileFoundHandler.HandleFileFoundAsync(file);
        await _sendHttpHandler.SendHttpAsync(file);
    }

    private FileInfo DoStuff()
    {
        // 原有业务逻辑,返回处理后的文件对象
        return new FileInfo("example.txt");
    }
}

3. 实现具体的处理类

每个处理类专注于自身逻辑,严格符合SRP:

// 文件发现逻辑的具体实现
public class FileFoundHandler : IFileFoundHandler
{
    public async Task HandleFileFoundAsync(FileInfo file)
    {
        // 这里实现文件搜索、记录等业务逻辑
        await Task.Delay(100); // 模拟异步操作
        Console.WriteLine($"处理文件发现事件:{file.FullName}");
    }
}

// HTTP发送逻辑的具体实现
public class SendHttpHandler : ISendHttpHandler
{
    public async Task SendHttpAsync(FileInfo file)
    {
        // 这里实现HTTP请求发送逻辑
        await Task.Delay(100); // 模拟异步操作
        Console.WriteLine($"发送HTTP请求,目标文件:{file.FullName}");
    }
}

4. 无副作用测试Worker类

使用Mock框架(如Moq)可以轻松模拟接口实现,验证Worker的核心逻辑:

[TestClass]
public class WorkerTests
{
    [TestMethod]
    public async Task ExecuteAsync_InvokesBothHandlers()
    {
        // Mock两个处理接口
        var fileFoundMock = new Mock<IFileFoundHandler>();
        var sendHttpMock = new Mock<ISendHttpHandler>();
        
        var worker = new Worker(fileFoundMock.Object, sendHttpMock.Object);
        await worker.ExecuteAsync();

        // 验证两个接口方法都被正确调用
        fileFoundMock.Verify(h => h.HandleFileFoundAsync(It.IsAny<FileInfo>()), Times.Once);
        sendHttpMock.Verify(h => h.SendHttpAsync(It.IsAny<FileInfo>()), Times.Once);
    }
}

解答你的疑问

1. 注入事件「主体」的实现形式?

这里的「事件主体」就是上述的接口实现类,通过构造函数注入到Worker中。Worker只依赖抽象接口,不关心具体实现——这符合依赖倒置原则,既降低了代码耦合,又能轻松替换不同的实现(比如测试用的Mock类、生产环境的业务类)。

2. 是否可在单个事件中注入文件搜索与HTTP发送逻辑?

技术上可以,但强烈不建议。这两个逻辑是完全独立的业务职责,放在同一个类中会违反SRP:

  • 修改其中一个逻辑时,可能意外影响另一个逻辑;
  • 无法单独复用或测试其中某一个逻辑;
  • 代码可读性和可维护性会显著下降。

如果确实需要同时触发两个逻辑,应该通过组合多个Handler的方式实现,而非将逻辑混在一个类中。

内容的提问来源于stack exchange,提问作者TryHard

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:40:59