事件依赖注入: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
相关产品推荐
相关产品推荐

