Language agnostic 如何设计一个允许定义验证的简单类结构?

Language agnostic 如何设计一个允许定义验证的简单类结构?,language-agnostic,Language Agnostic,我的公司处理纸质表格,以便将书面数据输入数据库。每个表单都有一组字段。我正在编写一个简单的内部应用程序来定义表单的字段,其中包括字段的验证。我现在想到的是类结构,我可以想到两种方法: 我可以编写一组类,每个类都表示一个验证,例如带有模式属性的RegexValidation,带有最小值和最大值的LengthValidation,等等。但是,如果将来出现任何新的验证类型,我可能会在项目中有很多地方需要编写新代码。我真的不认为会有任何新的验证类型,但作为一名程序员,这是我不应该做的假设 第二种方法是创

我的公司处理纸质表格,以便将书面数据输入数据库。每个表单都有一组字段。我正在编写一个简单的内部应用程序来定义表单的字段,其中包括字段的验证。我现在想到的是类结构,我可以想到两种方法:

  • 我可以编写一组类,每个类都表示一个验证,例如带有模式属性的RegexValidation,带有最小值和最大值的LengthValidation,等等。但是,如果将来出现任何新的验证类型,我可能会在项目中有很多地方需要编写新代码。我真的不认为会有任何新的验证类型,但作为一名程序员,这是我不应该做的假设


  • 第二种方法是创建一个抽象类Validation,所有验证器类都将从中继承。他们将拥有一个将参数名称映射到其值的字典(这样LengthValidation将有带有键“max”和“min”的项,RegexValidation将有带有键“pattern”的项,依此类推)。这看起来很不错,但有一个主要问题——在将数据写入数据库时,我必须知道哪个验证是哪个验证,以便将值放在适当的位置。我可以使用一个策略设计模式,让验证类拥有Save方法,这样每个类都知道它是如何保存在数据库中的。但另一方面,我不希望验证类负责将数据写入数据库


  • 那你有什么建议?还有其他想法吗?

    编写类层次结构很好。验证有许多验证的子类

    当新的验证出现时,旧的验证不需要重写。旧的东西仍然存在,仍然有效。当有人请求新的SomeNewIDNumberValidation时,EmailAddressValidation不会更改

    当然,如果您发现一个bug,就会重写一个类

    当您添加一个新的验证时,您将不会“在项目中有很多地方我将不得不编写新代码”。您将拥有新的验证和需要新验证的应用程序。没什么。这就是OO软件的最大优点——添加子类不会破坏任何东西

    您需要所有的验证器子类都有一个统一的“is_valid”方法。这就是如何使它们多态的。它们不需要相同的构造函数,只需要相同的验证器

    此外,您还需要每个验证器对象返回“已清理,可供数据库使用”值。一些验证器可以(也应该)清理它们的输入。删除信用卡号中的空格。例如,删除人们在电话号码中输入的任何奇怪标点符号

    您希望构建一个包含多个验证器的复合对象。我将使用Python语法,因为它更简单

    class SomeValidatableObject( object ):
        field1 = ThisValidator( size=12, otherSetting="B" )
        field2 = RegexValidator( expr=r"\d{3}-\d{2}" )
        field3 = SSNValidator()
        field4 = YetAnotherValidator( someSetting=9, size=14 )
    
    所有构造函数都是特定于验证的。所有逐字段验证都是一种常见的
    is\u valid
    方法。每个验证器都可以有一个
    clean_data
    方法

    您的复合对象可以有一个
    save
    方法,该方法从所有不同字段的值中构建生成的有效对象


    [这不是我设计的,我在描述使用的验证器。]如果您查看文档,您将看到它们是如何解决这个问题的。

    grails有一系列验证器:。考虑建立一个验证者层次结构,并使用一个从成员名到验证器的静态映射。当违反约束时,您可能需要考虑错误消息中的i18n。

    我不知道这是否符合您的问题,但是

    我最近与Microsofts模式和实践验证应用程序块一起工作——我非常喜欢它的实现


    如果你看看他们提供了什么,也许会给你一些想法。

    经过几个验证逻辑的项目后,我来到了第三个选项

    一般规则定义为:

    ///<summary>
    /// Typed delegate for holding the validation logics
    ///</summary>
    ///<param name="obj">Object to validate</param>
    ///<param name="scope">Scope that will hold all validation results</param>
    ///<typeparam name="T">type of the item to validate</typeparam>
    public delegate void Rule<T>(T obj, IScope scope);
    
    或者通过附件组成新的验证器

    /// <summary>
    /// Composes the string validator ensuring string length is shorter than
    /// <paramref name="maxLength"/>
    /// </summary>
    /// <param name="maxLength">Max string length.</param>
    /// <returns>new validator instance</returns>
    public static Rule<string> Limited(int maxLength)
    {
        Enforce.Argument(() => maxLength, Is.GreaterThan(0));
        return (s, scope) =>
        {
            if (s.Length > maxLength)
                scope.Error("String length can not be greater than '{0}'", maxLength);
        };
    }
    
    //
    ///组合字符串验证程序,确保字符串长度小于
    /// 
    /// 
    ///最大字符串长度。
    ///新验证器实例
    公共静态规则限制(int maxLength)
    {
    参数(()=>maxLength,大于(0));
    返回值,范围=>
    {
    如果(s.Length>maxLength)
    scope.Error(“字符串长度不能大于'{0}',maxLength”);
    };
    }
    
    规则可以组合在一起,并且仍然可读:

    internal static Rule<string>[] UserName = new[]
    {
        StringIs.Limited(6, 256),
        StringIs.ValidEmail
    };
    
    内部静态规则[]用户名=新[]
    {
    斯特林吉斯有限公司(6256),
    瓦利德梅尔
    };
    
    为了运行规则,只需向其传递一个对象、一个作用域(将输出写入),然后检查该作用域的结果

    此范例中的其他扩展点:

    • 通过检查作用域的当前错误级别,规则可以相互“通信”(即:如果已经存在问题,CPU密集型规则可能会跳过)
    • 您可以编写检查复杂业务对象的规则(通过利用IScope.Create)
    • 通过传入不同的范围实现,可以实现不同的规则行为(而不更改规则)。例如:

      • 快速参数作用域可用于检查函数参数,并在遇到第一个问题时立即引发异常
      • 验证范围可用于检查要抛出的函数参数以及遇到的所有问题的异常
      • 基于消息的作用域可用于通过某种错误提供程序将复杂域对象的规则故障绑定到UI元素(作用域路径用于此)
    还有更多的扩展
    public static void ValidEmail(string email, IScope scope)
    {
        if (!_emailRegex.IsMatch(email))
            scope.Error("String is not a valid email address");
    }
    
    /// <summary>
    /// Composes the string validator ensuring string length is shorter than
    /// <paramref name="maxLength"/>
    /// </summary>
    /// <param name="maxLength">Max string length.</param>
    /// <returns>new validator instance</returns>
    public static Rule<string> Limited(int maxLength)
    {
        Enforce.Argument(() => maxLength, Is.GreaterThan(0));
        return (s, scope) =>
        {
            if (s.Length > maxLength)
                scope.Error("String length can not be greater than '{0}'", maxLength);
        };
    }
    
    internal static Rule<string>[] UserName = new[]
    {
        StringIs.Limited(6, 256),
        StringIs.ValidEmail
    };