8

这个问题与语言无关,但我是 C# 人,所以我使用 POCO 一词来表示仅执行数据存储的对象,通常使用 getter 和 setter 字段。

我刚刚将我的域模型重新设计为超级骗子 POCO,并且对如何确保属性值在域中有意义存在一些担忧。

例如,服务的结束日期不应超过该服务所依据的合同的结束日期。但是,将检查放入 Service.EndDate 设置器似乎违反了 SOLID,更不用说随着需要完成的验证数量的增加,我的 POCO 类将变得混乱。

我有一些解决方案(将在答案中发布),但它们有其缺点,我想知道解决这个困境的一些最喜欢的方法是什么?

4

8 回答 8

7

我认为您从一个错误的假设开始,即您应该拥有除了存储数据之外什么都不做的对象,并且除了访问器之外没有任何方法。拥有对象的全部意义在于封装数据和行为。如果你有一个基本上只是一个结构的东西,你封装了什么行为?

于 2009-01-03T18:24:27.747 回答
3

我总是听到人们争论“Validate”或“IsValid”方法。

我个人认为这可能有效,但对于大多数 DDD 项目,您通常最终会得到多个验证,这些验证是允许的,具体取决于对象的特定状态。

所以我更喜欢“IsValidForNewContract”、“IsValidForTermination”或类似的,因为我相信大多数项目最终每个类都有多个这样的验证器/状态。这也意味着我没有接口,但我可以编写聚合的验证器,这些验证器可以很好地反映我所断言的业务条件。

我真的相信,在这种情况下,通用解决方案通常会将注意力重要的事情(代码正在做什么)上转移,以在技术优雅(接口、委托或其他方面)方面获得非常小的收益。就投票给我吧;)

于 2009-01-03T18:12:51.267 回答
3

我的一位同事提出了一个非常有效的想法。我们从来没有为它想出一个好名字,但我们称它为 Inspector/Judge。

检查员会查看一个对象并告诉您它违反的所有规则。法官将决定如何处理。这种分离让我们做一些事情。它让我们将所有规则放在一个地方(检查员),但我们可以有多个法官并根据上下文选择法官。

使用多个法官的一个例子是围绕客户必须有地址的规则展开​​的。这是一个标准的三层应用程序。在 UI 层,Judge 会生成一些 UI 可以用来指示必须填写的字段的内容。UI Judge 没有抛出异常。在服务层有另一个法官。如果它在保存期间发现没有地址的客户,它将引发异常。到那时,您真的必须阻止事情继续进行。

随着对象状态的变化,我们也有更严格的法官。这是一份保险申请,在报价过程中,允许将保单保存为不完整的状态。但是,一旦该策略准备好激活,就必须设置很多东西。所以服务端的报价法官没有激活法官那么严格。然而,Inspector 中使用的规则仍然是相同的,因此即使您决定不做任何事情,您仍然可以分辨出哪些是不完整的。

于 2009-10-14T19:22:05.313 回答
2

一种解决方案是让每个对象的 DataAccessObject 获取一个验证器列表。当 Save 被调用时,它会对每个验证器进行检查:

public class ServiceEndDateValidator : IValidator<Service> {
  public void Check(Service s) {
    if(s.EndDate > s.Contract.EndDate)
      throw new InvalidOperationException();
  }
}

public class ServiceDao : IDao<Service> {
  IValidator<Service> _validators;
  public ServiceDao(IEnumerable<IValidator<Service>> validators) {_validators = validators;}
  public void Save(Service s) {
    foreach(var v in _validators)
      v.Check(service);
    // Go on to save
  }
}

好处是非常明确的 SoC,缺点是在调用 Save() 之前我们不会得到检查。

于 2009-01-03T18:13:34.680 回答
2

过去,我通常将验证委托给它自己的服务,例如 ValidationService。原则上,这仍然符合 DDD 的理念。

在内部,这将包含一个验证器集合和一组非常简单的公共方法,例如 Validate(),它可以返回一个错误对象的集合。

很简单,在 C# 中是这样的

public class ValidationService<T>
{
  private IList<IValidator> _validators;

  public IList<Error> Validate(T objectToValidate)
  {
    foreach(IValidator validator in _validators)
    {
      yield return validator.Validate(objectToValidate);
    }
  }
}

验证器可以添加到默认构造函数中,也可以通过其他一些类(例如 ValidationServiceFactory)注入。

于 2009-01-03T20:19:31.867 回答
0

实际上,我认为这可能是逻辑的最佳位置,但这只是我。您可以使用某种 IsValid 方法来检查所有条件并返回真/假,也许是某种 ErrorMessages 集合,但这是一个不确定的主题,因为错误消息实际上并不是域模型的一部分。我有点偏见,因为我已经对 RoR 做了一些工作,这基本上就是它的模型所做的。

于 2009-01-03T18:11:25.620 回答
0

另一种可能性是让我的每个类都实现

public interface Validatable<T> {
  public event Action<T> RequiresValidation;
}

并让每个类的每个设置器在设置之前引发事件(也许我可以通过属性来实现)。

优点是实时验证检查。但更混乱的代码,不清楚谁应该做附件。

于 2009-01-03T18:18:21.863 回答
0

这是另一种可能性。验证是通过 Domain 对象上的代理或装饰器完成的:

public class ServiceValidationProxy : Service {
  public override DateTime EndDate {
    get {return EndDate;}
    set {
      if(value > Contract.EndDate)
        throw new InvalidOperationexception();
      base.EndDate = value;
    }
  }
}

优点:即时验证。可以通过 IoC 轻松配置。

缺点:如果是代理,经过验证的属性必须是虚拟的,如果是装饰器,所有域模型都必须是基于接口的。验证类最终会有点重量级——代理必须继承类,装饰器必须实现所有方法。命名和组织可能会令人困惑。

于 2009-01-04T01:46:12.443 回答