如何为模型匹配封闭泛型验证器?服务定位器是唯一方案吗?
如何为模型匹配封闭泛型验证器?服务定位器绝非唯一方案!
嘿,我之前也碰到过类似的场景——一堆派生类要对应各自的验证器,服务定位器真的不是唯一的选择,甚至不是最优解。结合你给出的Car类结构,咱们来聊几个更优雅的实现方式:
一、泛型验证器接口 + 依赖注入(首推)
这是最符合现代.NET开发规范的方式,完全避开服务定位器的弊端。
步骤1:定义通用验证器接口
先给所有验证器定一个统一的泛型接口:
public interface IValidator<T> { bool Validate(T model); // 也可以返回验证结果列表,比如 IEnumerable<ValidationError> Validate(T model); }
步骤2:为每个封闭类型实现验证器
比如给CarA、EngineA分别写对应的验证器,还能嵌套验证内部属性:
public class CarAValidator : IValidator<CarA> { // 注入子属性的验证器,实现递归验证 private readonly IValidator<Engine> _engineValidator; private readonly IValidator<Seat> _seatValidator; public CarAValidator(IValidator<Engine> engineValidator, IValidator<Seat> seatValidator) { _engineValidator = engineValidator; _seatValidator = seatValidator; } public bool Validate(CarA model) { bool isCarValid = model.ModelYear > 2010; // 验证CarA自身逻辑 bool isEngineValid = _engineValidator.Validate(model.Engine); bool areSeatsValid = model.Seats.All(s => _seatValidator.Validate(s)); return isCarValid && isEngineValid && areSeatsValid; } } public class EngineAValidator : IValidator<EngineA> { public bool Validate(EngineA model) { // 验证EngineA的逻辑 return model.Horsepower > 100 && model.FuelType == FuelType.Gasoline; } }
步骤3:批量注册验证器到DI容器
大部分DI框架都支持批量扫描注册泛型服务,不用手动一个个加:
- .NET原生DI:
var assembly = typeof(CarAValidator).Assembly; services.Scan(scan => scan .FromAssemblies(assembly) .AddClasses(classes => classes.AssignableTo(typeof(IValidator<>))) .AsImplementedInterfaces() .WithTransientLifetime()); - Autofac:
builder.RegisterAssemblyTypes(typeof(CarAValidator).Assembly) .AsClosedTypesOf(typeof(IValidator<>)) .InstancePerDependency();
步骤4:编写验证器提供者(可选)
如果需要动态根据类型获取验证器,可以写一个通用类,避免直接操作DI容器:
public class ValidatorProvider { private readonly IServiceProvider _serviceProvider; public ValidatorProvider(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } // 泛型调用,类型安全 public IValidator<T> GetValidator<T>() { return _serviceProvider.GetRequiredService<IValidator<T>>(); } // 非泛型调用,处理未知类型的实例 public IValidator GetValidator(Type modelType) { var validatorType = typeof(IValidator<>).MakeGenericType(modelType); return (IValidator)_serviceProvider.GetRequiredService(validatorType); } }
使用示例:
// 已知类型的场景 var carAValidator = validatorProvider.GetValidator<CarA>(); bool isValid = carAValidator.Validate(myCarA); // 未知类型的场景(比如处理Car基类实例) var carValidator = validatorProvider.GetValidator(myCar.GetType()); bool isValid = (bool)carValidator.GetType().GetMethod("Validate")?.Invoke(carValidator, new[] { myCar });
二、策略模式 + 类型映射字典
如果你的项目还没用到DI,或者不想引入泛型复杂度,可以用这种方式:
步骤1:定义非泛型验证器接口
public interface IValidator { bool Validate(object model); Type TargetType { get; } }
步骤2:实现具体验证器
public class CarAValidator : IValidator { public Type TargetType => typeof(CarA); public bool Validate(object model) { if (model is not CarA car) throw new ArgumentException("Invalid model type"); return car.ModelYear > 2000; } }
步骤3:构建验证器字典
启动时批量注册,避免手动写50+个条目:
var validators = new Dictionary<Type, IValidator>(); // 反射扫描所有验证器类型 var validatorTypes = typeof(IValidator).Assembly.GetTypes() .Where(t => typeof(IValidator).IsAssignableFrom(t) && !t.IsAbstract); foreach (var type in validatorTypes) { var validator = (IValidator)Activator.CreateInstance(type); validators.Add(validator.TargetType, validator); }
步骤4:使用验证器
public bool ValidateCar(Car car) { if (validators.TryGetValue(car.GetType(), out var validator)) { return validator.Validate(car); } throw new InvalidOperationException($"No validator found for {car.GetType().Name}"); }
三、反射 + 属性标记(灵活但性能稍弱)
如果需要更灵活的配置,可以给模型类直接标记对应的验证器:
步骤1:定义验证器标记属性
[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class ValidatorAttribute : Attribute { public Type ValidatorType { get; } public ValidatorAttribute(Type validatorType) { ValidatorType = validatorType; } }
步骤2:给模型类加标记
[Validator(typeof(CarAValidator))] public class CarA : Car { /* ... */ } [Validator(typeof(EngineAValidator))] public class EngineA : Engine { /* ... */ }
步骤3:编写验证器工厂
public static class ValidatorFactory { private static readonly Dictionary<Type, IValidator> _cache = new(); public static IValidator GetValidator(Type modelType) { if (_cache.TryGetValue(modelType, out var validator)) return validator; var attr = modelType.GetCustomAttribute<ValidatorAttribute>(); if (attr == null) throw new InvalidOperationException($"No validator attribute found for {modelType.Name}"); validator = (IValidator)Activator.CreateInstance(attr.ValidatorType); _cache.Add(modelType, validator); return validator; } }
使用示例:
var carA = new CarA(); var validator = ValidatorFactory.GetValidator(typeof(CarA)); bool isValid = validator.Validate(carA);
为什么不推荐服务定位器?
服务定位器模式虽然能解决问题,但它会隐藏依赖关系,让代码难以测试(需要模拟服务定位器),也违反了依赖注入的“显式依赖原则”。上面的几种方案要么通过DI显式注入,要么通过类型映射明确关联,代码的可读性和可维护性都更强。
内容的提问来源于stack exchange,提问作者Padraic Hickey
相关产品推荐
相关产品推荐

