【问题标题】:What design pattern to use for validating data and creating an object用于验证数据和创建对象的设计模式
【发布时间】:2013-05-14 10:18:03
【问题描述】:

我经常遇到这样的情况:我想通过传递一些给定的数据或另一个对象来创建对象的实例,但数据或对象需要有效或处于正确的状态。我总是对这样做的“正确”方式有点不清楚。这是我的例子:

鉴于这个类:

class BusinessObject()
{
    const Threshold = 10;

    public BusinessObject(SetOfData<SomeType> setofdata)
    {
        // an example of some validation
        if (setofdata.count > Threshold)
        {
            // performance some business logic
            // set properties
        }
    }
}

这样做可能会遇到一些问题:

var setofdata = new SetOfData<SomeType>();

// if data is not valid then the object will be created incorrectly
var businessObject = new BusinessObject(setofdata);

所以我的解决方案一直是:

class BusinessObjectBuilder()
{
    public BusinessObject Build(SetOfData<SomeType> setofdata)
    {
        // an example of some validation
        if (setofdata.count > Threshold)
            return new BusinessObject(setofdata);
        }
        else
        {
            return null;
        }
    }
}

或者将构造函数设为私有并添加静态工厂方法:

class BusinessObject()
{
    const Threshold = 10;

    public static Create(SetOfData<SomeType> setofdata)
    {
        if (setofdata.count > Threshold)
        {
            return new BusinessObject(setofdata);
        }
        else
        {
            return null;
        }
    }

    private BusinessObject(SetOfData<SomeType> setofdata)
    {
        // performance some business logic
        // set properties
    }
}

理想情况下,如果数据无效,我不希望抛出异常,因为在一个流程中可能会创建多个业务对象,并且如果一个验证失败并且捕获和抑制异常,我不希望整个流程失败不好。

此外,我读到的所有抽象工厂或工厂方法的示例都涉及传递某种类型或枚举以及正在构建和返回的正确对象。他们似乎从未涵盖过这种情况。

那么在这个场景中的约定是什么?任何建议将不胜感激。

【问题讨论】:

  • 请注意,工厂并不需要传入“某种类型或枚举”;他们可以获取任何类型的数据(甚至是SetOfData&lt;SomeType&gt;)或根本没有数据(无参数)。这些示例倾向于使用它们,因为这是使用/描述它们的一种相当常见/简单的方式。如果您愿意,您始终可以为工厂创建一个 BusinessObjectValidator 以利用它来检查参数,但如果检查像您描述的那样简单,我会尽快将其放入工厂创建方法中。

标签: c# java design-patterns


【解决方案1】:

恕我直言,构造函数验证最适用于需要确保除非设置指定参数才能创建任何对象的许多情况。

public class BusinessObject
{
    const Threshold = 10;

    public BusinessObject(SetOfData<SomeType> setofdata)
    {
        // an example of some validation
        if (setofdata.count > Threshold)
        {
            throw new InvalidOperationException("Set data must be above treshold");
        }
    }
}

但是,在以下情况下,它的实现很糟糕:

  • 您可能有无效对象,例如处于草稿状态等
  • 需要默认构造函数时在 ORM 中使用
  • 如果出现繁重的验证逻辑。

对于第 1 点和第 2 点,除了请求 - 验证 - 提交机制,我无法建议任何其他选项。

对于第 3 点,原因是,该类将在验证本身和创建单一代码方面做太多事情。如果验证逻辑比较多,建议使用注入验证器实现builder模式,将BusinessObject的构造函数设为internal。

public class BusinessObjectBuilder
{
    public BusinessObjectBuilder(IBusinessObjectValidator validator){
        this.validator = validator;
    }
    IBusinessObjectValidator validator;

    public BusinessObject Build(SetOfData<SomeType> setofdata)
    {
        // an example of some validation
        if (validator.IsValid(setofdata))
            return new BusinessObject(setofdata);
        }
        else
        {
            throw new //exception
        }
    }
}

这会强制模块化编程并防止单一代码。

这两个代码都是:

  • 易于测试
  • 易于查看
  • 可扩展

【讨论】:

    【解决方案2】:

    也许您可以将Strategy Pattern 实现到您的Factory (method) 以提供一些验证功能:

    public interface DataValidationStrategy {
        boolean isValid(SetOfData<SomeType> setofdata);
    }
    
    public class ThresholdValidation implements DataValidationStrategy {
        private int threshold;
    
        public ThresholdValidation(int threshold) {
            this.threshold = threshold;
        }
    
        @Override
        public booleam isValid(SetOfData<SomeType> setofdata) {
            return setofdata.count > threshold;
        }
    }
    

    现在根据需要创建多个不同的validation classes,然后更改create 方法:

    public static Create(SetOfData<SomeType> setofdata, DataValidationStrategy validation)
    {
        if (validation.isValid(setofData))
        {
            return new BusinessObject(setofdata);
        }
        else
        {
            return null;
        }
    }
    

    编辑:此外,您可以考虑使用prototypenull object 而不是null 返回值。

    【讨论】:

    • 我相信空对象在这种情况下是有害的。消费者可以检查 null 并假设如果返回的 BusinessObject 不为 null,则它是正确创建的。并且检查 Create 返回的对象是否不是 Null Object 将是错误使用模式的一个示例。
    【解决方案3】:

    我认为,当参数不正确时,从 Create 之类的方法中抛出异常是一般做法。您对在这种情况下返回null 并试图避免使用异常进行流控制持保留意见是正确的。你可以简单地拥有:

    public bool CanBuild(SetOfData<SomeType> setofdata)
    {
        return validator.IsValid(setofdata);
    }
    
    public BusinessObject Build(SetOfData<SomeType> setofdata)
    {
        if (validator.IsValid(setofdata))
        {
            return new BusinessObject(setofdata);
        }
        throw new ArgumentException();
    }
    

    我仍然会抛出异常,因为无法保证 setofdata 已使用 CanBuild 进行验证。这提供了避免使用异常进行流控制的方法,但有两次验证的缺点。要消除双重验证,请将TryCreate 放在Create 旁边或代替Create。只需查看签名,您就会发现,除了创建业务对象之外,此方法还执行输入数据的验证,并以除异常之外的形式返回此验证的结果。 (见int.Parseint.TryParse

    public bool TryBuild(SetOfData<SomeType> setofdata, out BusinessObject businessObject)
    {
        if (validator.IsValid(setofdata))
        {
            businessObject = new BusinessObject(setofdata);
            return true;
        }
        businessObject = null;
        return false;
    }
    

    这种验证方法将调用其他不包含验证的构造函数,而所有可广泛访问的构造函数仍会验证并抛出异常。

    当然方法BuiltTryBuilt 可以是静态的(如int),或者您可以应用工厂模式。

    【讨论】:

      【解决方案4】:

      我无法说出“正确”的方式。但我可以告诉你我的方式 =)

      我会选择工厂模式。如果您不想因验证错误而中止应用程序,工厂可以使用默认值填充有故障的部分。

      【讨论】:

        【解决方案5】:
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-07
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多