简体   繁体   English

规范模式实现帮助

[英]Specification pattern implementation help

I've a question regarding enforcing a business rule via a specification pattern. 我有一个关于通过规范模式实施业务规则的问题。 Consider the following example: 请考虑以下示例:

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()
    {
    }
}

The business rule should enforce that there cannot be a child in the collection which validity period intersects with another. 业务规则应该强制在集合中不存在有效期与另一个相交的子节点。

For that I would like to implement a specification that is then be used to throw an exception if an invalid child is added AND as well can be used to check whether the rule will be violated BEFORE adding the child. 为此,我想实现一个规范,如果添加了无效的子节点,则该规范用于抛出异常,并且在添加子节点之前也可以用于检查规则是否会被违反。

Like: 喜欢:


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

But in this example the child accesses the parent. 但在此示例中,子进程访问父进程。 And to me that doesnt seem that correct. 而对我来说似乎并不正确。 That parent might not exist when the child has not been added to the parent yet. 当孩子尚未添加到父母时,该父母可能不存在。 How would you implement it? 你会如何实现它?

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

UPDATE: 更新:

But of course, the real power of the specification pattern is when you can plug in and combine different rules. 但是,当然,规范模式的真正强大之处在于您可以插入并组合不同的规则。 You can have an interface like this (possibly with a better name): 您可以拥有这样的界面(可能有更好的名称):

public interface ISpecification {
  bool IsSatisfiedBy(Parent parent, Child candidate);
}

And then use it like this on Parent : 然后在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);
  }
}

Child would be simple: Child会很简单:

public class Child {
  internal Parent Parent { get; set; }
  public DateTime ValidFrom;
  public DateTime ValidTo;
}

And you could implement multiple specifications, or composite specifications. 您可以实现多个规范或复合规范。 This is the one from your example: 这是你的例子中的一个:

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

Note that it makes more sense to make Child 's public data immutable (only set through the constructor) so that no instance can have its data changed in a way that would invalidate a Parent . 请注意,使Child的公共数据不可变(仅通过构造函数设置)更有意义,这样任何实例都不会以使Parent无效的方式更改其数据。

Also, consider encapsulating the date range in a specialized abstraction . 另外,考虑将日期范围封装在专门的抽象中

I think the Parent should probably do the validation. 我认为家长应该做验证。 So in the parent you might have a canBeParentOf(Child) method. 所以在父级中你可能有一个canBeParentOf(Child)方法。 This method would also be called at the top of your AddChild method--then the addChild method throws an exception if canBeParentOf fails, but canBeParentOf itself does not throw an exception. 此方法也将在AddChild方法的顶部调用 - 然后addChild方法在canBeParentOf失败时抛出异常,但canBeParentOf本身不会抛出异常。

Now, if you want to use "Validator" classes to implement canBeParentOf, that would be fantastic. 现在,如果你想使用“Validator”类来实现canBeParentOf,那就太棒了。 You might have a method like validator.validateRelationship(Parent, Child). 您可能有一个像validator.validateRelationship(Parent,Child)这样的方法。 Then any parent could hold a collection of validators so that there could be multiple conditions preventing a parent/child relationship. 然后,任何父级都可以拥有一组验证器,以便可能有多个条件阻止父/子关系。 canBeParentOf would just iterate over the validators calling each one for the child being added--as in validator.canBeParentOf(this, child);--any false would cause canBeParentOf to return a false. canBeParentOf将遍历验证器,为被添加的子项调用每个验证器 - 如validator.canBeParentOf(this,child); - 任何false都会导致canBeParentOf返回false。

If the conditions for validating are always the same for every possible parent/child, then they can either be coded directly into canBeParentOf, or the validators collection can be static. 如果每个可能的父/子的验证条件总是相同,那么它们可以直接编码到canBeParentOf中,或者验证器集合可以是静态的。

An aside: The back-link from child to parent should probably be changed so that it can only be set once (a second call to the set throws an exception). 旁白:应该更改从子级到父级的反向链接,以便只能设置一次(对该组的第二次调用会引发异常)。 This will A) Prevent your child from getting into an invalid state after it's been added and B) detect an attempt to add it to two different parents. 这将是A)防止您的孩子在被添加后进入无效状态并且B)检测到将其添加到两个不同父母的尝试。 In other words: Make your objects as close to immutable as possible. 换句话说:尽可能使对象尽可能接近不可变。 (Unless changing it to different parents is possible). (除非将其改为不同的父母是可能的)。 Adding a child to multiple parents is obviously not possible (from your data model) 显然不可能将子项添加到多个父项(从您的数据模型)

您是否没有If语句来检查父级是否为空,如果是,则返回false?

You are trying to guard against Child being in an invalid state. 您正试图防止Child处于无效状态。 Either

  • use the builder pattern to create fully populated Parent types so that everything you expose to the consumer is always in a valid state 使用构建器模式创建完全填充的Parent类型,以便向消费者公开的所有内容始终处于有效状态
  • remove the reference to the Parent completely 完全删除对Parent的引用
  • have Parent create all instances of Child so this can never occur Parent创建Child所有实例,这样就永远不会发生

The latter case might look (something) like this (in Java): 后一种情况可能看起来像这样(在Java中):

public class DateRangeHolder {
  private final NavigableSet<DateRange> ranges = new TreeSet<DateRange>();

  public void add(Date from, Date to) {
    DateRange range = new DateRange(this, from, to);
    if (ranges.contains(range)) throw new IllegalArgumentException();
    DateRange lower = ranges.lower(range);
    validate(range, lower);
    validate(range, ranges.higher(lower == null ? range : lower));
    ranges.add(range);
  }

  private void validate(DateRange range, DateRange against) {
    if (against != null && range.intersects(against)) {
      throw new IllegalArgumentException();
    }
  }

  public static class DateRange implements Comparable<DateRange> {
    // implementation elided
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM