zoukankan      html  css  js  c++  java
  • 实现一个对象验证库系列 -- 1) 接口介绍以及总体思路概述 (请大神批评)

    前情回顾:

    上一篇 0) 目录以及库结构介绍 简单描述了下库的代码结构

    本文将从接口部分阐述总体的思路

    1) 接口介绍以及总体思路概述

    如下图,我总共定义了10个Interface

    这些实际可分为两类:

    • 为了支持 Fluent 语法格式而定义的各个创建者接口
      • IFluentRuleBuilder
      • IRuleBuilder
      • IRuleMessageBuilder
      • IValidateRuleBuilder
      • IValidatorBuilder
      • IValidatorSetter
    • 验证操作涉及的规则、结果、验证调用接口的定义:
      • IRuleSelector
      • IValidateResult
      • IValidateRule
      • IValidator

    接下来我们首先阐述下验证使用方式的接口设计思路,

    然后再介绍 Fluent 格式的规则设置方式的设计思路。

    (1)验证使用方式的接口设计思路

    我们首先考虑的用户的验证使用方式,而且我们是提供用户自行设置验证规则,不是只是提供一些固定的验证规则,

    那么用户其实只是想给一个数据,然后拿到对应的结果就行

    所以大致接口设想就是

    public interface IValidator
    {
    	object Validate(object data);
    }
    

    方法是这样,用户拿到返回值怎么方便使用呢?

    思考一番,思路都是类似如下的

    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; }
    }
    
    // 所以接口可以改成这样 public interface IValidator { IValidateResult Validate(object data); }

    思考一下参数,object肯定不是满足的:

    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);
    }
    

    接口方法这样可以了,但是验证规则如何保存呢?思考如下:

    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 的理念,想一下我们如何创建规则呢?

    是不是这样呢?

    new ValidateRule() 
    {
    	RuleSet = "xx",
    	ValueName = "xx"
    	.....
    }
    

    回忆一下 Fluent 的方式,链式的语法,而且在我们这里用来设置验证规则,简直就是创建者模式的链式使用而已

    ValidateRule.SetRuleSet("xx").SetValueName("xx")
    

     所以如下我们有了规则的创建者

    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 设计时都是大致设想最终效果类似什么样,才能建立对应的接口这些定义

    我们回想一下我们的最终效果是什么样呢?

    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");
    

    解释一下我们最终的效果:

    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");
    

    总体来说我们将一个规则的设置分成了几个阶段,并且这些阶段是不可逆的,有着严格顺序的

    • 初始创建父级验证规则,并会设置对应验证的属性
    • 设置如何验证方法
    • 填写一些描述信息或者建立子级规则

    如此我们的接口便会是如下:

    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) 验证器实现

  • 相关阅读:
    Docker(二十一)-Docker Swarm集群部署
    Docker(十八)-Docker配置DNS
    Docker(十七)-修改Docker容器启动配置参数
    Docker(十六)-Docker的daemon.json的作用
    JavaScript实现无缝滚动 原理详细讲解
    JS 数字 、中文、 英文、判断
    JS常用属性方法大全
    vue.js插件使用(01) vue-resource
    Vue.js常见问题
    web前端开发必备技术
  • 原文地址:https://www.cnblogs.com/fs7744/p/4894591.html
Copyright © 2011-2022 走看看