基于字典查找的策略模式实现验证引擎的优化问询
首先得说,你当前用策略模式+Lazy延迟加载的方案整体是合理的:Lazy加载避免了一次性创建所有验证器实例,内存控制做得很到位;策略模式也完美解耦了不同属性的验证逻辑,每个验证器只负责单一职责,完全符合开闭原则。但问题也很明显——手动维护35+个属性与验证器的映射会非常繁琐,后期属性名变更、验证策略调整时,很容易漏改或出错,维护成本太高。
下面给你几个更优的实现思路,解决手动映射的痛点:
方案1:基于特性(Attribute)的配置驱动(最推荐)
通过给输入对象的属性打特性标记,自动扫描并构建验证器映射,彻底告别手动维护字典。
步骤1:定义验证器特性
先创建一个特性类,用来标记属性对应的验证器类型:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class ValidatorAttribute : Attribute { public Type ValidatorType { get; } public ValidatorAttribute(Type validatorType) { if (!typeof(IValidationEngine).IsAssignableFrom(validatorType)) throw new ArgumentException("Validator type must implement IValidationEngine"); ValidatorType = validatorType; } }
步骤2:给输入对象的属性打标记
假设你的载荷是强类型类(比如Payload),直接在属性上标记对应的验证器:
public class Payload { [Validator(typeof(MandatoryStringLengthValidator))] public string Position { get; set; } [Validator(typeof(StringLengthValidator))] public string Pcode { get; set; } [Validator(typeof(MandatoryStringLengthValidator))] public string Username { get; set; } // 其他35个属性依次打标记即可 }
步骤3:改造ValidationRouter自动构建映射
通过反射扫描Payload类的属性,读取特性自动生成Lazy验证器字典:
public class ValidationRouter { private readonly Dictionary<string, Lazy<IValidationEngine>> _strategies = new(); public ValidationRouter() { // 扫描Payload类的所有公共实例属性 foreach (var prop in typeof(Payload).GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var validatorAttr = prop.GetCustomAttribute<ValidatorAttribute>(); if (validatorAttr == null) continue; // 用Lazy延迟创建验证器实例 var lazyValidator = new Lazy<IValidationEngine>(() => (IValidationEngine)Activator.CreateInstance(validatorAttr.ValidatorType)); // 保持属性名转小写的逻辑和之前一致 _strategies.Add(prop.Name.ToLower(), lazyValidator); } } public Error Execute(string name, dynamic value) { var loweredName = name.ToLower(); if (_strategies.TryGetValue(loweredName, out var validator)) { return validator.Value.Validate(name, value); } return default(Error); } }
后续新增属性只需要给属性打特性,完全不用修改ValidationRouter,维护成本直接降为0。
方案2:配置文件驱动(适合动态调整场景)
如果需要在不修改代码的情况下调整验证策略,可以用配置文件(比如appsettings.json)定义映射关系:
{ "ValidationMappings": { "position": "YourNamespace.MandatoryStringLengthValidator", "pcode": "YourNamespace.StringLengthValidator", "username": "YourNamespace.MandatoryStringLengthValidator", // 其他属性映射... } }
然后在ValidationRouter中读取配置构建字典:
public class ValidationRouter { private readonly Dictionary<string, Lazy<IValidationEngine>> _strategies = new(); public ValidationRouter(IConfiguration configuration) { var mappings = configuration.GetSection("ValidationMappings").Get<Dictionary<string, string>>(); if (mappings == null) return; foreach (var (propName, validatorTypeName) in mappings) { var validatorType = Type.GetType(validatorTypeName); if (validatorType == null || !typeof(IValidationEngine).IsAssignableFrom(validatorType)) { // 这里可以加日志记录配置错误 continue; } _strategies.Add(propName.ToLower(), new Lazy<IValidationEngine>(() => (IValidationEngine)Activator.CreateInstance(validatorType))); } } // Execute方法和之前一致 }
这个方案的优势是动态可调,但需要注意验证器类型名称的正确性,避免配置错误。
方案3:组合验证器简化冗余代码
你当前的MandatoryStringLengthValidator其实是MandatoryValidator和StringLengthValidator的组合,可以用组合验证器复用逻辑,减少冗余类:
public class CompositeValidator : IValidationEngine { private readonly IEnumerable<IValidationEngine> _validators; public CompositeValidator(params IValidationEngine[] validators) { _validators = validators; } public Error Validate(string propName, dynamic propValue) { // 按顺序执行验证,返回第一个错误(也可以收集所有错误,根据需求调整) foreach (var validator in _validators) { var error = validator.Validate(propName, propValue); if (error != default) return error; } return default(Error); } }
然后扩展特性支持多个验证器类型:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class ValidatorsAttribute : Attribute { public Type[] ValidatorTypes { get; } public ValidatorsAttribute(params Type[] validatorTypes) { ValidatorTypes = validatorTypes; } }
属性上就可以直接标记组合验证:
[Validators(typeof(MandatoryValidator), typeof(StringLengthValidator))] public string Position { get; set; }
最后在ValidationRouter中创建组合验证器实例即可,彻底告别MandatoryStringLengthValidator这类冗余类。
总结
你的初始方案是合理的,但手动映射扩展性不足。优先推荐基于特性的配置驱动方案,既保留了策略模式的解耦优势,又极大降低了维护成本;如果需要动态调整验证策略,再考虑配置文件驱动;组合验证器则可以进一步简化代码结构,符合DRY原则。
内容的提问来源于stack exchange,提问作者Karthik Chintala

