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
.
Также рассмотрите возможность включения диапазона дат в специализированную абстракцию .