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

时间:2009-01-12 10:59:31

标签: language-agnostic

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

  1. 我可以编写一组类,每个类代表一次验证,例如 RegexValidation 及其 Pattern 属性, LengthValidation ,其 Min Max 等等。但是,如果将来出现任何新的验证类型,我可能会在项目中有很多地方需要编写新代码。我真的不认为会有任何新的验证类型,但这是一个我不应该作为程序员的假设。

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

  3. 所以你会建议什么?还有其他想法吗?

4 个答案:

答案 0 :(得分:1)

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

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

如果你发现了一个错误,当然会重写一个类。

当你添加一个新的验证时,你不会“在项目中有很多地方我必须编写新的代码”。您将获得新验证和需要新验证的应用程序。什么都没有。这是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_valid方法。每个验证器都可以使用clean_data方法。

您的复合对象可以使用save方法从所有各个字段的值构建生成的有效对象。

[我没有设计这个,我正在描述Django Project使用的验证器。]如果你查看Forms文档,你会看到他们如何解决这个问题。 / p>

答案 1 :(得分:0)

grails有一堆验证器:http://www.grails.org/Validation+Reference。考虑构建验证器层次结构并使用从成员名称到验证器的静态映射。当违反约束时,您可能想要考虑错误消息的i18n。

答案 2 :(得分:0)

我不知道这是否完全符合你的问题但是......

我最近使用过Microsofts模式和实践验证应用程序块 - 我真的很喜欢实现http://msdn.microsoft.com/en-us/library/cc309509.aspx

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

答案 3 :(得分:0)

在几个带有验证逻辑的项目之后,我来到了第三个选项。

通用规则定义为:

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

IScope

/// <summary>
/// Concept from the xLim2. That's simple nesting logger that is used by
/// the validation rules. 
/// </summary>
public interface IScope : IDisposable
{
    /// <summary>
    /// Creates the nested scope with the specified name.
    /// </summary>
    /// <param name="name">New name for the nested scope.</param>
    /// <returns>Nested (and linked) scope instance</returns>
    IScope Create(string name);

    /// <summary>
    /// Writes <paramref name="message"/> with the specified
    /// <paramref name="level"/> to the <see cref="IScope"/>
    /// </summary>
    /// <param name="level">The level.</param>
    /// <param name="message">The message.</param>
    void Write(RuleLevel level, string message);

    /// <summary>
    /// Gets the current <see cref="RuleLevel"/> of this scope
    /// </summary>
    /// <value>The level.</value>
    RuleLevel Level { get; }
}

规则级别是:

/// <summary>
/// Levels leveraged by the <see cref="Rule{T}"/> implementations
/// </summary>
public enum RuleLevel
{
    /// <summary> Default value for the purposes of good citizenship</summary>
    None = 0,
    /// <summary> The rule raises a warning </summary>
    Warn,
    /// <summary> The rule raises an error </summary>
    Error
}

即使没有声明新类,您也可以定义新规则:

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

为了运行规则,您只需传递一个对象,一个范围(将输出写入),然后检查范围以获得结果。

此范例中的其他可扩展性点:

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

    • 快速参数范围可用于检查函数参数,并在遇到第一个问题时立即抛出异常
    • 验证范围可用于检查抛出的函数参数和遇到所有遇到的问题的异常
    • MessageBased范围可用于通过一些错误提供程序将复杂域对象的规则失败绑定到UI元素(范围路径用于此)

在这个简单的概念中有更多的可扩展性点))

BTW,Open Source implementation适用于.NET。

PS:在我们公司,我们已经在单个文件中定义了一些公共API规则(相当小),并在各处重用这些规则进行验证。