2010-01-28 10 views
5

Ho una domanda riguardante l'applicazione di una regola aziendale tramite un modello di specifica. Si consideri il seguente esempio:Guida all'implementazione del modello di specifiche

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

regola L'azienda dovrebbe far rispettare che non ci può essere un bambino nella collezione che periodo di validità si interseca con un altro.

Per questo vorrei implementare una specifica che verrà poi utilizzata per generare un'eccezione se viene aggiunto un figlio non valido E anche per controllare se la regola verrà violata PRIMA di aggiungere il bambino.

come:


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

Ma in questo esempio, il bambino accede al genitore. E a me non sembra corretto. Quel genitore potrebbe non esistere quando il bambino non è stato ancora aggiunto al genitore. Come lo implementeresti?

risposta

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

UPDATE:

Ma, naturalmente, il vero potere del modello specifica è quando è possibile collegare e combinare regole diverse. Si può avere un'interfaccia simile a questo (magari con un nome migliore):

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

e quindi utilizzarlo come questo sul 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 sarebbe semplice:

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

E potresti implementare più specifiche o specifiche composite. Questo è quello che dal vostro esempio:

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

Nota che ha più senso per rendere Child 's dati pubblici immutabili (impostato solo tramite il costruttore) in modo che nessuna istanza può avere i suoi dati modificati in modo tale da invalida un Parent.

Inoltre, considerare di incapsulare l'intervallo di date in un specialized abstraction.

0

Non si dispone di un'istruzione If per verificare che un genitore non sia nullo e in caso affermativo restituisca false?

+0

Questa può essere una possibilità. Ma mi sto solo chiedendo se sto usando questo modello nel modo giusto ... La validità non sarebbe unica se non ci fosse un genitore? – Chris

2

Penso che il genitore dovrebbe probabilmente fare la convalida. Quindi nel genitore potresti avere un metodo canBeParentOf (Child). Questo metodo verrà anche chiamato all'inizio del metodo AddChild, quindi il metodo addChild genera un'eccezione se canBeParentOf non riesce, ma canBeParentOf non genera un'eccezione.

Ora, se si desidera utilizzare le classi "Validator" per implementare canBeParentOf, sarebbe fantastico. Potresti avere un metodo come validator.validateRelationship (Parent, Child). Quindi qualsiasi genitore potrebbe contenere una raccolta di validatori in modo che possano esserci più condizioni che impediscono una relazione genitore/figlio. canBeParentOf eseguirà semplicemente un'iterazione sui validatori che chiamano ognuno per il figlio aggiunto, come in validator.canBeParentOf (this, child); - qualsiasi falso causerebbe canBeParentOf per restituire un falso.

Se le condizioni per la convalida sono sempre le stesse per ogni possibile genitore/figlio, possono essere codificate direttamente in canBeParentOf oppure la raccolta di validatori può essere statica.

A parte: il back-link da figlio a genitore dovrebbe probabilmente essere cambiato in modo che possa essere impostato solo una volta (una seconda chiamata all'insieme lancia un'eccezione). A) Impedisce a tuo figlio di entrare in uno stato non valido dopo che è stato aggiunto e B) rileva un tentativo di aggiungerlo a due genitori diversi. In altre parole: Rendi i tuoi oggetti il ​​più possibile immutabili. (A meno che non sia possibile cambiarlo con genitori diversi). L'aggiunta di un figlio a più genitori non è ovviamente possibile (dal modello dei dati)

0

Stai cercando di evitare che Child si trovi in ​​uno stato non valido.In entrambi i

  • utilizzare il builder per creare completamente popolato Parent tipi in modo che tutto ciò che si espone al consumatore è sempre in uno stato valido
  • rimuovere il riferimento alla Parent completamente
  • avere Parent creare tutte le istanze di Child quindi questo non potrà mai accadere

quest'ultimo caso potrebbe apparire (qualcosa) come questo (in Java):

012.
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 
    } 
}