8

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

给定这个类:

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

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

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

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

4

5 回答 5

11

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

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模式,将构造器设为BusinessObjectinternal。

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

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

两个代码都是:

  • 易于测试
  • 易于审查
  • 可扩展
于 2013-05-14T10:41:53.723 回答
2

也许您可以为您实施策略模式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;
    }
}

编辑:此外,您可能会考虑使用原型空对象而不是null返回值。

于 2013-05-14T10:33:46.810 回答
1

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

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

于 2013-05-14T10:25:42.000 回答
1

我相信通常的做法是从方法中抛出异常,例如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. 只需查看签名,您就会发现,除了创建业务对象之外,此方法还执行输入数据的验证,并以除异常之外的形式返回此验证的结果。(见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;
}

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

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

于 2013-05-14T21:30:26.717 回答
-1

看看OVal框架。您可以使用自己的注释对其进行扩展。

于 2013-05-15T12:50:09.740 回答