2010-09-24 23 views
3

Je suis coincé avec ce qui semblait être une tâche très simple au tout début. J'ai une hiérarchie de classe dans laquelle chaque classe peut définir ses propres règles de validation. Définir les règles de validation devrait être aussi simple que possible. Voici à peu près ce dont vous avez besoin:Comment valider des classes dans une hiérarchie d'une manière générique en toute sécurité?

class HierarchyBase 
{ 

    private List<Func<object, bool>> rules = new List<Func<object, bool>>(); 
    public int fieldA = 0; 

    public HierarchyBase() 
    { 
     AddRule(x => ((HierarchyBase)x).fieldA % 2 == 0); 
    } 

    protected virtual void Operation() 
    { 
     fieldA++; 
    } 

    protected void AddRule(Func<object, bool> validCriterion) 
    { 
     rules.Add(validCriterion); 
    } 

    public void PerformOperation() 
    { 
     Operation(); 
     Validate(); 
    } 

    protected virtual void Operation() 
    { 
     fieldA++; 
    } 

    private void Validate() 
    { 
     IsValid = rules.All(x => x(this)); 
    } 

    public bool IsValid 
    { 
     get; 
     private set; 
    } 
} 

Il y a encore une chose à faire: sécuriser le type lors de l'ajout de règles de validation. Sinon, chaque sous-classe devra faire ces moulages qui semblent juste gênants. Idéalement, Func<T, bool> fonctionnerait, mais il y a tout un tas de problèmes avec cela: nous ne pouvons pas hériter de HierarchyBase de n'importe quel type de IValidatable<HierarchyBase> car la hiérarchie d'héritage peut être N niveaux profonds (ouais, je sens l'odeur aussi); stocker du béton Func<HierarchyBaseInheritor, bool> dans rules et de les traverser.

Comment introduiriez-vous la sécurité de type ici?

+0

Je ne sais pas, je comprends le problème, quel est le problème avec l'introduction de médicaments génériques et en utilisant '' Func avec '' HierarchyBase ? – TheCodeKing

Répondre

3

La bonne approche est de faire de chaque classe dans la hiérarchie responsable de se valider:

HierarchyBase:

class HierarchyBase 
{ 
    public int A { get; set; } 

    public bool Validate() 
    { 
     return this.OnValidate(); 
    } 

    protected virtual bool OnValidate() 
    { 
     return (this.A % 2 == 0); 
    } 
} 

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA : HierarchyBase 
{ 
    public int B { get; set; } 

    protected override bool OnValidate() 
    { 
     return base.OnValidate() && 
       (this.A > 10) && 
       (this.B != 0); 
    } 
} 

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA 
{ 
    public int C { get; set; } 

    protected override bool OnValidate() 
    { 
     return base.OnValidate() && 
       (this.A < 20) && 
       (this.B > 0) && 
       (this.C == 0); 
    } 
} 

Utilisation:

var result = new HierarchyBaseInheritorB(); 
result.A = 12; 
result.B = 42; 
result.C = 0; 
bool valid = result.Validate(); // == true 
+1

+1. L'utilisation de lambdas ne gagne rien ici; la méthode AddRule est protégée, donc les jeux de règles ne peuvent pas être injectés en externe. – KeithS

+0

Ces règles ne sont pas des invariants de classe et les règles de validation peuvent être assez complexes. Les Lambdas sont utilisées simplement pour la simplicité et peuvent être remplacées par, par exemple, l'objet ValidationRule.Nous n'allons pas mélanger différentes responsabilités avec ces classes en séparant les règles. C'est au lieu de 'rendre chaque classe de la hiérarchie responsable de la validation' que nous 'rendons chaque classe de la hiérarchie responsable de la définition de ses règles de validation' –

+0

@dbt: Dans la POO classique c'est soit cet héritage soit les événements avec e.Annuler . J'ai juste l'impression que OP essayait d'obtenir une solution plus "déclarative" que "fonctionnelle". Ah BTW, aimé votre -based réponse :) –

3

Note: La solution suivante est une blague en entre moi et Eric Lippert. Cela fonctionne, mais n'est probablement pas à recommander.

L'idée est de définir un paramètre de type générique qui fait référence au type "courant" (comme this fait référence à l'objet "courant").

HierarchyBase:

class HierarchyBase<T> 
    where T : HierarchyBase<T> 
{ 
    protected readonly List<Func<T, bool>> validators; 

    public HierarchyBase() 
    { 
     validators = new List<Func<T, bool>>(); 
     validators.Add(x => x.A % 2 == 0); 
    } 

    public int A { get; set; } 

    public bool Validate() 
    { 
     return validators.All(validator => validator((T)this)); 
    } 
} 

HierarchyBaseInheritorA:

class HierarchyBaseInheritorA<T> : HierarchyBase<T> 
    where T : HierarchyBaseInheritorA<T> 
{ 
    public HierarchyBaseInheritorA() 
    { 
     validators.Add(x => x.A > 10); 
     validators.Add(x => x.B != 0); 
    } 

    public int B { get; set; } 
} 

HierarchyBaseInheritorB:

class HierarchyBaseInheritorB : HierarchyBaseInheritorA<HierarchyBaseInheritorB> 
{ 
    public HierarchyBaseInheritorB() 
    { 
     validators.Add(x => x.A < 20); 
     validators.Add(x => x.B > 0); 
     validators.Add(x => x.C == 0); 
    } 

    public int C { get; set; } 
} 

Utilisation:

var result = new HierarchyBaseInheritorB(); 
result.A = 12; 
result.B = 42; 
result.C = 0; 
bool valid = result.Validate(); // == true 
+0

J'avais cette solution à l'arrière de ma tête mais je ne voulais même pas le remarquer :) –