规范模式实现帮助

Chr*_*ris 5 .net c# java oop domain-driven-design

我有一个关于通过规范模式实施业务规则的问题.请考虑以下示例:

public class Parent
{
    private ICollection<Child> children;

    public ReadOnlyCollection Children { get; }

    public void AddChild(Child child)
    {
        child.Parent = this;
        children.Add(child);
    }
}


public class Child
{
    internal Parent Parent
    {
        get;
        set;
    }

    public DateTime ValidFrom;
    public DateTime ValidTo;

    public Child()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

业务规则应该强制在集合中不存在有效期与另一个相交的子节点.

为此,我想实现一个规范,如果添加了一个无效的子节点,则该规范用于抛出异常,并且在添加子节点之前也可以用于检查规则是否会被违反.

喜欢:


public class ChildValiditySpecification
{
    bool IsSatisfiedBy(Child child)
    {
        return child.Parent.Children.Where(<validityIntersectsCondition here>).Count > 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

但在此示例中,子进程访问父进程.而对我来说似乎并不正确.当孩子尚未添加到父母时,该父母可能不存在.你会如何实现它?

Jor*_*dão 6

public class Parent {
  private List<Child> children;

  public ICollection<Child> Children { 
    get { return children.AsReadOnly(); } 
  }

  public void AddChild(Child child) {
    if (!child.IsSatisfiedBy(this)) throw new Exception();
    child.Parent = this;
    children.Add(child);
  }
}

public class Child {
  internal Parent Parent { get; set; }

  public DateTime ValidFrom;
  public DateTime ValidTo;

  public bool IsSatisfiedBy(Parent parent) { // can also be used before calling parent.AddChild
    return parent.Children.All(c => !Overlaps(c));
  }

  bool Overlaps(Child c) { 
    return ValidFrom <= c.ValidTo && c.ValidFrom <= ValidTo;
  }
}
Run Code Online (Sandbox Code Playgroud)

更新:

但是,当然,规范模式的真正强大之处在于您可以插入并组合不同的规则.您可以拥有这样的界面(可能有更好的名称):

public interface ISpecification {
  bool IsSatisfiedBy(Parent parent, Child candidate);
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它Parent:

public class Parent {
  List<Child> children = new List<Child>();
  ISpecification childValiditySpec;
  public Parent(ISpecification childValiditySpec) {
    this.childValiditySpec = childValiditySpec;
  }
  public ICollection<Child> Children {
    get { return children.AsReadOnly(); }
  }
  public bool IsSatisfiedBy(Child child) {
    return childValiditySpec.IsSatisfiedBy(this, child);
  }
  public void AddChild(Child child) {
    if (!IsSatisfiedBy(child)) throw new Exception();
    child.Parent = this;
    children.Add(child);
  }
}
Run Code Online (Sandbox Code Playgroud)

Child 会很简单:

public class Child {
  internal Parent Parent { get; set; }
  public DateTime ValidFrom;
  public DateTime ValidTo;
}
Run Code Online (Sandbox Code Playgroud)

您可以实现多个规范或复合规范.这是你的例子中的一个:

public class NonOverlappingChildSpec : ISpecification {
  public bool IsSatisfiedBy(Parent parent, Child candidate) {
    return parent.Children.All(child => !Overlaps(child, candidate));
  }
  bool Overlaps(Child c1, Child c2) {
    return c1.ValidFrom <= c2.ValidTo && c2.ValidFrom <= c1.ValidTo;
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,使Child公共数据不可变(仅通过构造函数设置)更有意义,这样任何实例都不会以使a无效的方式更改其数据Parent.

另外,考虑将日期范围封装在专门的抽象中.