Справка реализации шаблона спецификации

У меня есть вопрос относительно осуществления бизнес-правила через шаблон спецификации. Рассмотрите следующий пример:

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

Бизнес-правило должно осуществить это не может быть ребенка в наборе, который срок действия пересекает с другим.

Для этого я хотел бы реализовать спецификацию, которая является затем использоваться для выдачи исключения, если недопустимый ребенок добавляется И также может использоваться, чтобы проверить, будет ли правило нарушено ПРЕЖДЕ, ЧЕМ добавить ребенка.

Как:


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

Но в этом примере дочерние доступы родитель. И мне, который не кажется, это исправляет. Тот родитель не мог бы существовать, когда ребенок еще не был добавлен к родителю. Как Вы реализовали бы его?

5
задан Chris 28 January 2010 в 21:31
поделиться

4 ответа

Это зависит от языка. Самый простой способ (концептуально): поиск обоих и убедитесь, что оба не совпадают. В Ruby:

s = "FileNTile"
(s !~ /File/) and (s !~ /Tile)  # true if s is free of files and tiles.
-121--1906170-
^((?!File|Tile).)*$

Это вряд ли будет хорошей идеей, хотя. Почти каждая среда программирования будет иметь более четкий и более эффективный подход с совпадением строк. (например, Python: , если "File" не в s и "Tile" не в s )

Также не во всех реализациях regex выполняется поиск. например. он не надёжен в JavaScript. И могут возникнуть проблемы с новыми линиями в зависимости от режима (мультилиния, флажки dotall).

-121--1906168-
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
ответ дан 14 December 2019 в 04:38
поделиться

Я думаю, что Родители, вероятно, должны провести валидацию. Так что в родителях вы можете использовать метод canBeParentOf(Child). Этот метод также будет вызываться в верхней части вашего метода AddChild - тогда метод addChild бросает исключение, если canBeParentOf не удается, но сам метод canBeParentOf не бросает исключение.

Теперь, если вы хотите использовать классы "Validator" для реализации canBeParentOf, это будет фантастика. У вас может быть такой метод как validator.validateRelationship(Parent, Child). Тогда любой родитель может содержать набор валидаторов, так что может быть несколько условий, препятствующих отношениям родитель/ребенок. canBeParentOf будет просто выполнять итерации над валидаторами, вызывающими каждый из них для добавляемого дочернего элемента - как в validator.canBeParentOf(this, child);-- любое ложное срабатывание приведет к тому, что canBeParentOf вернет ложное срабатывание.

Если условия валидации всегда одинаковы для каждого возможного родителя/ребенка, то они могут быть либо закодированы непосредственно в canBeParentOf, либо коллекция валидаторов может быть статической.

В сторону: Скорее всего, следует изменить обратную связь от дочернего к родительскому так, чтобы она могла быть установлена только один раз (второй вызов сета вызывает исключение). Это позволит A) не допустить, чтобы ваш ребенок попал в недействительное состояние после добавления и B) обнаружить попытку добавить его к двум разным родителям. Другими словами: Сделайте свои объекты как можно ближе к неизменяемым. (Если только не возможно изменить его на разных родителей). Добавление ребёнка к нескольким родителям очевидно невозможно (из вашей модели данных)

.
2
ответ дан 14 December 2019 в 04:38
поделиться

У вас не будет ли вы утверждение, чтобы проверить, что родитель не был нулевым, и если это так, вернуть false?

0
ответ дан 14 December 2019 в 04:38
поделиться

Вы пытаетесь охранять против ребенка в неверном состоянии. Либо

  • используют шаблон Builder для создания полностью заполненных родитель типов типов , чтобы все, что вы обнарили потребителю, всегда в действительном состоянии
  • Удалите ссылку на родитель
  • У родитель Создайте все экземпляры ребенка Так что это никогда не может произойти

Последний случай может выглядеть (что-то), подобное этому (в 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
ответ дан 14 December 2019 в 04:38
поделиться