使用依赖注入(DI)在类内部重建损坏APIClient的最佳方案
实现方案说明
你的包装类方案合理性
你提出的IMyAPIClient包装类方案完全合理,符合依赖倒置原则,不会让业务类和DI容器产生强耦合,适合需要在多个依赖IMyAPIClient的类中复用重建、重试逻辑的场景。
同时还有更轻量的实现方案可选,你可以根据自己的业务场景选择:
方案1:注入工厂委托(最轻量,无需新增类)
你不需要在业务类中直接引用DI容器,只需要在构造函数中注入一个异步工厂委托,把客户端创建逻辑完全放到DI组合根处理即可,修改后的代码如下:
public class MyObject { protected IMyAPIClient Client { get; set; } // 注入异步工厂委托,不关心客户端具体创建逻辑 private readonly Func<Task<IMyAPIClient>> _clientFactory; private const int MAX_ATTEMPTS = 3; public MyObject(Func<Task<IMyAPIClient>> clientFactory) { _clientFactory = clientFactory; // 初始化首次客户端实例 Client = _clientFactory().GetAwaiter().GetResult(); } protected async Task<ReturnType> Run<ReturnType>(Func<Task<ReturnType>> action, int attempt = 1) { try { return await action(); } catch(Exception exception) { // 直接通过工厂获取新实例,无DI容器依赖 Client = await _clientFactory(); if(attempt > MAX_ATTEMPTS) { throw new Exception($"Failed {attempt} times", exception); } // 注意原代码的attempt++是后置自增,会导致传入递归的还是原值引发死循环,这里改为attempt+1 return await Run(action, attempt + 1); } } }
DI容器注册示例(以ASP.NET Core内置DI为例),所有客户端创建逻辑都在组合根处理,业务代码完全不感知DI容器存在:
services.AddSingleton<MyObject>(sp => { return new MyObject(async () => { // 这里可以从DI容器获取工厂创建新实例,也可以直接写创建逻辑 var clientFactory = sp.GetRequiredService<IMyAPIClientFactory>(); return await clientFactory.CreateNewAsync(); }); });
方案2:自动恢复包装类(适合多场景复用)
如果有多个类依赖IMyAPIClient,你可以把重建、重试逻辑全部封装到包装类中,原有业务代码不需要做任何修改:
// 实现IMyAPIClient接口,作为可自动恢复的包装类 public class RecoverableMyAPIClient : IMyAPIClient { private IMyAPIClient _innerClient; private readonly Func<Task<IMyAPIClient>> _clientFactory; private const int MAX_ATTEMPTS = 3; public RecoverableMyAPIClient(Func<Task<IMyAPIClient>> clientFactory) { _clientFactory = clientFactory; _innerClient = _clientFactory().GetAwaiter().GetResult(); } // 实现IMyAPIClient的所有接口方法,统一封装重试逻辑 public async Task<TResult> CallSomeApiAsync<TParam, TResult>(TParam param) { int attempt = 1; while(attempt <= MAX_ATTEMPTS) { try { return await _innerClient.CallSomeApiAsync<TParam, TResult>(param); } catch { _innerClient = await _clientFactory(); attempt++; if(attempt > MAX_ATTEMPTS) throw; } } throw new InvalidOperationException("重试次数耗尽,API客户端恢复失败"); } }
DI注册时直接把包装类注册为IMyAPIClient的实现即可,原有依赖IMyAPIClient的业务类不需要修改一行代码:
// 注册可自动恢复的客户端实现 services.AddScoped<IMyAPIClient, RecoverableMyAPIClient>(sp => { return new RecoverableMyAPIClient(async () => { // 真实客户端创建逻辑 return new RealMyAPIClient(/* 配置参数 */); }); }); // 原有MyObject注册不需要修改 services.AddScoped<MyObject>();
内容的提问来源于stack exchange,提问作者Martien de Jong
相关产品推荐
相关产品推荐

