实现一个对象验证库系列 -- 1) 接口介绍以及总体思路概述 (请大神批评)
前情回顾:
上一篇 0) 目录以及库结构介绍 简单描述了下库的代码结构
本文将从接口部分阐述总体的思路
1) 接口介绍以及总体思路概述
如下图,我总共定义了10个Interface
这些实际可分为两类:
- 为了支持 Fluent 语法格式而定义的各个创建者接口:
- IFluentRuleBuilder
- IRuleBuilder
- IRuleMessageBuilder
- IValidateRuleBuilder
- IValidatorBuilder
- IValidatorSetter
- 验证操作涉及的规则、结果、验证调用接口的定义:
- IRuleSelector
- IValidateResult
- IValidateRule
- IValidator
接下来我们首先阐述下验证使用方式的接口设计思路,
然后再介绍 Fluent 格式的规则设置方式的设计思路。
(1)验证使用方式的接口设计思路
我们首先考虑的用户的验证使用方式,而且我们是提供用户自行设置验证规则,不是只是提供一些固定的验证规则,
那么用户其实只是想给一个数据,然后拿到对应的结果就行
所以大致接口设想就是
1 2 3 4 | public interface IValidator { object Validate( object data); } |
方法是这样,用户拿到返回值怎么方便使用呢?
思考一番,思路都是类似如下的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public interface IValidateResult { bool IsValid { get ; } // 使用者肯定首先在乎验证时候通过 List<ValidateFailure> Failures { get ; } // 如果不通过,会有什么错误信息 } // 错误信息的类,使用者一般都是关注那个地方出错,出了什么错,原始的值是什么才导致了错误 public class ValidateFailure { public string Name { get ; set ; } public object Value { get ; set ; } public string Error { get ; set ; } } <br> // 所以接口可以改成这样 public interface IValidator { IValidateResult Validate( object data); } |
思考一下参数,object肯定不是满足的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | public class ValidateContext { public IRuleSelector RuleSelector { get ; set ; } // 很多时候,用户可能只是使用其中一些验证规则,比如验证学生信息时,大学生和幼儿园同学肯定是不一样的 // 所以用户得有一个实现如何选择验证规则方式的规则选择器 public IEnumerable< string > RuleSetList { get ; set ; } // 大部分时候,规则选择器都是一样,但是规则选择只需用户设置一些标志,一个默认实现的规则选择器基本够用了 public ValidateOption Option { get ; set ; } // 有时候用户可能需要全部错误,但是有时会先check null,不为null才做其他验证 public object ValidateObject { get ; set ; } } public enum ValidateOption { StopOnFirstFailure, Continue } public interface IRuleSelector { bool CanExecute(IValidateRule rule, ValidateContext context); } // 所以最终接口这样就可以了 public interface IValidator { IValidateResult Validate(ValidateContext context); } |
接口方法这样可以了,但是验证规则如何保存呢?思考如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public interface IValidateRule { string RuleSet { get ; set ; } // 规则分组的标志,大多数情况就可以满足用户对一个对象不同情境的验证分组 IValidateRule NextRule { get ; set ; } // 一个规则与其他规则常有关联性,比如先check 为null,然后再check 长度,都放在一个class中肯定不方便我们定义与使用 string ValueName { get ; set ; } // 有check 数据的名字属性,这样用户可以改变这个名字 string Error { get ; set ; } // 有展示错误的属性,这样用户可以改变这个属性值 Func<ValidateContext, bool > Condition { get ; set ; } // 规则分组如果不满足用户,就只能提供这样的func让用户自行筛选了 Func<ValidateContext, string , string , IValidateResult> ValidateFunc { get ; set ; } // 之所以有这个属性,是为了便利地拓展check的logic,不必每个新的规则check方式都必须写一个ValidateRule类 IValidateResult Validate(ValidateContext context); // 提供规则调用的接口 } |
(2)Fluent 格式的规则设置方式的设计思路
上面我们以及思考到了如何保存验证规则,那么我们如何用 Fluent 的方式设置规则呢?
首先我们抛开 Fluent 的理念,想一下我们如何创建规则呢?
是不是这样呢?
1 2 3 4 5 6 | new ValidateRule() { RuleSet = "xx" , ValueName = "xx" ..... } |
回忆一下 Fluent 的方式,链式的语法,而且在我们这里用来设置验证规则,简直就是创建者模式的链式使用而已
1 | ValidateRule.SetRuleSet( "xx" ).SetValueName( "xx" ) |
所以如下我们有了规则的创建者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public interface IValidateRuleBuilder { string RuleSet { get ; set ; } string ValueName { get ; set ; } string Error { get ; set ; } Func<ValidateContext, bool > Condition { get ; set ; } IValidateRuleBuilder NextRuleBuilder { get ; set ; } Func<ValidateContext, string , string , IValidateResult> ValidateFunc { get ; set ; } IValidateRule Build(); // 大致与IValidateRule差不多,只是多了这个Build 方法 } public interface IValidatorBuilder<T> { IFluentRuleBuilder<T, TProperty> RuleFor<TProperty>(Expression<Func<T, TProperty>> expression); void RuleSet( string ruleSet, Action<IValidatorBuilder<T>> action); IValidator Build(); } |
Fluent 设计时都是大致设想最终效果类似什么样,才能建立对应的接口这些定义
我们回想一下我们的最终效果是什么样呢?
1 2 3 4 5 6 7 8 | ValidatorBuilder.RuleFor(i => i.Age) .Must(i => i >= 0 && i <= 18) .OverrideName( "student age" ) .OverrideError( "not student" ) .ThenRuleFor(i => i.Name) .Must(i => ! string .IsNullOrWhiteSpace(i)) .OverrideName( "student name" ) .OverrideError( "no name" ); |
解释一下我们最终的效果:
1 2 3 4 5 6 7 8 9 10 11 | ValidatorBuilder.RuleFor(i => i.Age) //一个 ValidatorBuilder 才能创建父级验证规则,并会设置对应验证的属性 .Must(i => i >= 0 && i <= 18) // 每个规则必须设置如何验证方法 .OverrideName( "student age" ) .OverrideError( "not student" ) // 设置一些需要复写的信息 .ThenRuleFor(i => i.Name) // 只有设置完了一个规则的验证方法之后才能建立子级规则 .Must(i => ! string .IsNullOrWhiteSpace(i)) .OverrideName( "student name" ) .OverrideError( "no name" ); |
总体来说我们将一个规则的设置分成了几个阶段,并且这些阶段是不可逆的,有着严格顺序的
- 初始创建父级验证规则,并会设置对应验证的属性
- 设置如何验证方法
- 填写一些描述信息或者建立子级规则
如此我们的接口便会是如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public interface IValidatorBuilder<T> { IFluentRuleBuilder<T, TProperty> RuleFor<TProperty>(Expression<Func<T, TProperty>> expression); void RuleSet( string ruleSet, Action<IValidatorBuilder<T>> action); IValidator Build(); } // 对应 初始创建父级验证规则,并会设置对应验证的属性 这个阶段 public interface IFluentRuleBuilder<T, TProperty> { } // 对应 设置如何验证方法 这个阶段 public interface IRuleMessageBuilder<T, TValue> { IFluentRuleBuilder<T, TProperty> ThenRuleFor<TProperty>(Expression<Func<T, TProperty>> expression); } // 对应 填写一些描述信息或者建立子级规则 这个阶段 public interface IRuleBuilder<T, TValue> : IValidateRuleBuilder, IRuleMessageBuilder<T, TValue>, IFluentRuleBuilder<T, TValue> { Func< object , TValue> ValueGetter { get ; } Expression<Func<T, TValue>> ValueExpression { get ; } void SetValueGetter(Expression<Func<T, TValue>> expression); } // 总结三个阶段的最终定义 |
以上全部就是接口的设计思路
后面将慢慢的描述如何实现
NEXT: 2) 验证器实现
分类:
c#
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?