【问题标题】:Data Validation Design Patterns数据验证设计模式
【发布时间】:2010-09-07 22:39:16
【问题描述】:

如果我有一组数据库表(例如,在 Access 文件中)并且需要根据一个规则集验证该集合中的每个表,该规则集具有跨所有表的通用规则以及特定于一个或表的一个子集,有人可以推荐一个好的设计模式来研究吗?

具体来说,我想避免类似的代码:

void Main()
{
    ValidateTable1();
    ValidateTable2();
    ValidateTable3();
}

private void ValidateTable1()
{
    //Table1 validation code goes here
}

private void ValidateTable2()
{
    //Table2 validation code goes here
}

private void ValidateTable3()
{
    //Table3 validation code goes here
}

另外,我决定使用 log4net 来记录所有错误和警告,这样每个方法都可以声明为 void 并且不需要返回任何内容。这是一个好主意,还是创建某种ValidationException 来捕获所有异常并将它们存储在List<ValidationException> 中,然后再将它们全部打印出来会更好?

我确实找到了this,看起来它可能会起作用,但我希望能真正找到一些可以解决的代码示例。有什么建议?过去有没有人做过类似的事情?

在某些背景下,该程序将使用 C# 或 VB.NET 编写,并且表很可能存储在 Access 或 SQL Server CE 中。

【问题讨论】:

    标签: design-patterns validation oop


    【解决方案1】:

    我会为每个返回某种类型的 ValidationSummary...或 IList,具体取决于您希望如何构建它。

    你也可以选择做一些这样的魔法:

    using(var validation = new ValidationScope())
    {
       ValidateTable1();
       ValidateTable2();
       ValidateTable3();
    
       if(validation.Haserrors)
       {
           MessageBox.Show(validation.ValidationSummary);
           return;
       }
    
       DoSomethingElse();
    }
    

    那么 ValidateTable 就会进入当前范围,如下所示:

    ValidationScope.Current.AddError("col1", "Col1 should not be NULL");
    

    类似的东西。

    【讨论】:

    • 设计模式非常好,但通常像这样的简单解决方案可以解决问题。
    • 很好奇,这个设计模式有什么知名的名字吗?
    【解决方案2】:

    只是对此的更新:我决定使用Decorator pattern。也就是说,我有一个实现IValidateableTable 接口(包含validate() 方法)的“通用”表类。然后,我创建了几个验证装饰器(也是implement IValidateableTable),我可以将它们包裹在我要验证的每个表周围。

    所以,代码最终看起来像这样:

    IValidateableTable table1 = new GenericTable(myDataSet);
    table1 = new NonNullNonEmptyColumnValidator(table1, "ColumnA");
    table1 = new ColumnValueValidator(table1, "ColumnB", "ExpectedValue");
    

    然后,我需要做的就是调用table1.Validate(),它通过调用所有需要的验证的装饰器展开。到目前为止,它似乎工作得很好,尽管我仍然愿意接受建议。

    【讨论】:

      【解决方案3】:

      两种方法:

      1. CSLA 使用业务对象上的匿名方法进行验证。
      2. 阅读JP Boodhoo's 博客,他在其中实现了规则引擎,并发布了非常详细的帖子和示例代码。您还可以在 DNR Tv 剧集中看到他在工作,非常值得一看。

      【讨论】:

        【解决方案4】:

        我认为您实际上是在谈论数据库领域中称为constraints 的概念。约束是数据库如何保证其包含的数据的完整性。将这种逻辑放在数据库中而不是应用程序中更有意义(甚至 Access 也提供了基本形式的约束,例如要求列中值的唯一性或列表中的值等)。
        (单个字段的)输入验证当然是另一回事,任何应用程序仍应执行该操作(以便在出现问题时向用户提供良好的反馈),即使数据库具有明确定义的表列约束。

        【讨论】:

          【解决方案5】:

          我会尝试结合使用工厂模式和访客模式:

          using System;
          using System.Collections.Generic;
          
          namespace Example2
          {
              interface IVisitor
              {
                  void Visit(Table1 table1);
                  void Visit(Table2 table2);
              }
          
              interface IVisitable
              {
                  void Accept(IVisitor visitor);
              }
          
              interface ILog
              {
                  void Verbose(string message);
                  void Debug(string messsage);
                  void Info(string message);
                  void Error(string message);
                  void Fatal(string message);
              }
          
              class Error
              {
                  public string Message { get; set; }
              }
          
              class Table1 : IVisitable
              {
                  public int Id { get; set; }
                  public string Data { get; set; }
                  private IList<Table2> InnerElements { get; } = new List<Table2>();
          
                  public void Accept(IVisitor visitor)
                  {
                      visitor.Visit(this);
          
                      foreach(var innerElement in InnerElements)
                          visitor.Visit(innerElement);
                  }
              }
          
              class Table2 : IVisitable
              {
                  public int Id { get; set; }
                  public int Data { get; set; }
          
                  public void Accept(IVisitor visitor)
                  {
                      visitor.Visit(this);
                  }
              }
          
              class Validator : IVisitor
              {
                  private readonly ILog log;
                  private readonly IRuleSet<Table1> table1Rules;
                  private readonly IRuleSet<Table2> table2Rules;
          
                  public Validator(ILog log, IRuleSet<Table1> table1Rules, IRuleSet<Table2> table2Rules)
                  {
                      this.log = log;
                      this.table1Rules = table1Rules;
                      this.table2Rules = table2Rules;
                  }
          
                  public void Visit(Table1 table1)
                  {
                      IEnumerable<Error> errors = table1Rules.EnforceOn(table1);
          
                      foreach (var error in errors)
                          log.Error(error.Message);
                  }
          
                  public void Visit(Table2 table2)
                  {
                      IEnumerable<Error> errors = table2Rules.EnforceOn(table2);
          
                      foreach (var error in errors)
                          log.Error(error.Message);
                  }
              }
          
              class RuleSets
              {
                  private readonly IRuleSetFactory factory;
          
                  public RuleSets(IRuleSetFactory factory)
                  {
                      this.factory = factory;
                  }
          
                  public IRuleSet<Table1> RulesForTable1 =>
                      factory.For<Table1>()
                          .AddRule(o => string.IsNullOrEmpty(o.Data), "Data1 is null or empty")
                          .AddRule(o => o.Data.Length < 10, "Data1 is too short")
                          .AddRule(o => o.Data.Length > 26, "Data1 is too long");
          
                  public IRuleSet<Table2> RulesForTable2 =>
                      factory.For<Table2>()
                          .AddRule(o => o.Data < 0, "Data2 is negative")
                          .AddRule(o => o.Data > 10, "Data2 is too big");
              }
          
              interface IRuleSetFactory
              {
                  IRuleSet<T> For<T>();
              }
          
              interface IRuleSet<T>
              {
                  IEnumerable<Error> EnforceOn(T obj);
                  IRuleSet<T> AddRule(Func<T, bool> rule, string description);
              }
          
              class Program
              {
                  void Run()
                  {
                      var log = new ConsoleLogger();
                      var factory = new SimpleRules();
                      var rules = new RuleSets(factory);
                      var validator = new Validator(log, rules.RulesForTable1, rules.RulesForTable2);
          
                      var toValidate = new List<IVisitable>();
                      toValidate.Add(new Table1());
                      toValidate.Add(new Table2());
          
                      foreach (var validatable in toValidate)
                          validatable.Accept(validator);
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2012-12-30
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-06-30
            相关资源
            最近更新 更多