C#中如何调用泛型接口实现优化多验证器调用逻辑
你当前实现的核心问题是MyService承担了多余的依赖协调职责:既需要知道Person包含多少个待验证子属性,还要维护每个子属性对应验证器的引用,后续Person结构调整时必须同步修改MyService代码,违反开闭原则。以下是可落地的优化方案:
方案1:职责聚合实现IValidator<Person>(最推荐)
这是最符合泛型接口设计初衷的方案:MyService不需要感知所有子验证器的存在,仅依赖IValidator<Person>即可,所有子属性的验证协调逻辑全部收敛到Person专属验证器中,后续验证规则调整完全不影响上层服务。
第一步:补全接口层级解决泛型统一收集问题
新增非泛型版IValidator作为公共基接口,同时提供抽象基类减少重复代码:
// 非泛型基接口,用于统一收集所有验证器实例 public interface IValidator { ValidationResults Validate(object instance); } // 泛型接口继承非泛型基接口 public interface IValidator<T> : IValidator { ValidationResults Validate(T instance); } // 抽象基类,统一实现非泛型接口的类型转换逻辑,避免每个验证器重复写类型校验 public abstract class ValidatorBase<T> : IValidator<T> { public abstract ValidationResults Validate(T instance); ValidationResults IValidator.Validate(object instance) { if (instance is not T typedInstance) throw new ArgumentException($"传入参数类型不符合,期望类型:{typeof(T).FullName}", nameof(instance)); return Validate(typedInstance); } }
第二步:改造现有子属性验证器
所有子属性验证服务继承ValidatorBase<T>即可,不需要修改原有验证逻辑。注意你贴的代码存在笔误:AddressDetailsValidationService的泛型参数应该是AddressDetails,不是BasicDetails,否则运行时会出现类型不匹配问题。
改造后示例:
public class BasicDetailsValidationService : ValidatorBase<BasicDetails> { public override ValidationResults Validate(BasicDetails instance) { // 原有BasicDetails验证逻辑 } } public class AddressDetailsValidationService : ValidatorBase<AddressDetails> { public override ValidationResults Validate(AddressDetails instance) { // 原有AddressDetails验证逻辑 } } // ContactDetailsValidationService、FeeValidationService按相同规则实现即可
第三步:实现Person专属验证器
在该类中统一协调所有子属性的验证逻辑,提供两种实现写法可选:
- 写法A:显式注入子验证器(无反射、性能最高,适合属性数量固定的场景)
public class PersonValidator : ValidatorBase<Person> { private readonly IValidator<BasicDetails> _basicValidator; private readonly IValidator<AddressDetails> _addressValidator; private readonly IValidator<ContactDetails> _contactValidator; private readonly IValidator<Fee> _feeValidator; // 所有子验证器依赖全部收敛到此处,上层服务完全无感知 public PersonValidator( IValidator<BasicDetails> basicValidator, IValidator<AddressDetails> addressValidator, IValidator<ContactDetails> contactValidator, IValidator<Fee> feeValidator) { _basicValidator = basicValidator; _addressValidator = addressValidator; _contactValidator = contactValidator; _feeValidator = feeValidator; } public override ValidationResults Validate(Person instance) { var results = new ValidationResults(); // 依次调用各属性验证,合并结果 results.Merge(_basicValidator.Validate(instance.BasicDetails)); results.Merge(_addressValidator.Validate(instance.AddressDetails)); results.Merge(_contactValidator.Validate(instance.ContactDetails)); results.Merge(_feeValidator.Validate(instance.Fee)); return results; } }
- 写法B:批量注入自动匹配属性(无需手动维护子验证器列表,后续新增子属性/验证器不需要修改本类代码)
public class PersonValidator : ValidatorBase<Person> { private readonly IEnumerable<IValidator> _allValidators; // 缓存属性与对应验证器的映射,避免每次验证都执行反射 private readonly Dictionary<Type, IValidator> _validatorMap; // 直接注入所有已注册的验证器实例 public PersonValidator(IEnumerable<IValidator> allValidators) { _allValidators = allValidators; // 构造函数中一次性完成类型匹配缓存 _validatorMap = _allValidators.ToDictionary( v => v.GetType().GetInterfaces() .First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IValidator<>)) .GetGenericArguments()[0], v => v); } public override ValidationResults Validate(Person instance) { var results = new ValidationResults(); foreach (var prop in typeof(Person).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var propValue = prop.GetValue(instance); if (propValue == null) continue; if (_validatorMap.TryGetValue(prop.PropertyType, out var validator)) { results.Merge(validator.Validate(propValue)); } } return results; } }
提示:上述代码中
ValidationResults.Merge是用于合并多个验证结果的方法,如果你当前的ValidationResults类没有该方法,自行补充对应逻辑即可。
第四步:简化MyService逻辑
改造后MyService仅需要注入一个IValidator<Person>实例,代码量大幅降低:
public class MyService : IMyService { private readonly IValidator<Person> _personValidator; public MyService(IValidator<Person> personValidator) { _personValidator = personValidator; } public async Task ValidateAsync(Person pe) { var validationResults = _personValidator.Validate(pe); if (!validationResults.IsValid) { // 原有验证失败处理逻辑:抛异常、返回错误信息等 } // 后续业务逻辑 } }
第五步:DI批量注册
主流DI容器(微软内置DI、Autofac等)都支持开放泛型批量注册,不需要逐个编写服务注册代码,以微软内置DI为例:
// 扫描程序集批量注册所有IValidator实现 builder.Services.Scan(scan => scan .FromAssembliesOf(typeof(PersonValidator)) // 替换为验证器所在的实际程序集 .AddClasses(classes => classes.AssignableTo(typeof(IValidator))) .AsImplementedInterfaces() .WithScopedLifetime()); // 生命周期按实际需求调整即可
方案2:轻量实现(不新增PersonValidator)
如果你暂时不想拆分独立的Person验证器,也可以直接在MyService中注入IEnumerable<IValidator>,按照方案1中写法B的逻辑自动匹配属性类型调用验证器。但该方案的缺点是验证协调逻辑仍然耦合在业务服务中,后续其他地方需要验证Person时无法复用逻辑。
内容的提问来源于stack exchange,提问作者Learner213

