Specification Template Implementation Help

I have a question regarding the application of a business rule through a specification template. 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 must state that the collection cannot have a child whose validity period overlaps with another.

To do this, I would like to implement a specification that will then be used to throw an exception if an invalid child is added, and can also be used to check whether the rule is violated BEFORE the child is added.

how


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

But in this example, the child refers to the parent element. And this does not seem right to me. This parent may not exist if the child has not yet been added to the parent. How do you implement it?

+5
4
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:

, , , . (, ):

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

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

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

. :

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

, Child ( ), , Parent.

.

+6

, , , . , canBeParentOf (Child). AddChild - addChild , canBeParentOf , canBeParentOf .

, "Validator" canBeParentOf, . , validator.validateRelationship(Parent, Child). , , /. canBeParentOf , - validator.canBeParentOf(, child); - false , canBeParentOf false.

/ , canBeParentOf, .

: , , , ( ). A) B) . : . ( , ). , , ( )

+2

If, , , false?

0

Child .

  • use the builder template to create fully populated types Parentso that everything you show to the consumer is always in a valid state
  • completely remove the link to Parent
  • have Parentcreate all instances Childso this never happens

The latter case may look (something) like this (in 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
  }
}
0
source

All Articles