2009-08-21 5 views
7

Il y a beaucoup d'implémentations Fluent qui fonctionnent avec Lambda pour faire des choses qui sont assez soignées. Je voudrais envelopper mon cerveau pour que je puisse commencer à créer certaines de ces choses, mais je n'ai pas encore trouvé une explication que mon cerveau comprend.Lambda Func <> et Fluent

Considérons cet exemple simple d'une personne Validator

public class PersonValidator : IValidator<Person> 
{ 
    public PersonValidator() 
    { 
      AddRule(p => p.FirstName).CannotBeNull().CannotBeBlank(); 
      AddRule(p => p.LastName).CannotBeNull().CannotBeBlank(); 
    } 

    public List<ValidationResult> Validate(Person p) 
    { 
     // pseudo... 
     apply all rules specified in constructor, return results 
    } 
} 

J'ai réussi à faire partie de tout cela en utilisant une méthode de travail sur mon validateur comme ça ...

public ValidationResult<T,TProp> AddRule<T,TProp>(Func<T,TProp> property) 
{ 
    ... not sure what to do here. This method gives me the ability to use the lambda 
    ... for specifying which properties i want to validate 
} 

I Vous pouvez ensuite créer des méthodes Extension qui étendent IValidator aux fins de CannotBeNull et CannotBeEmpty.

Donc, il semble que j'ai la première moitié et la deuxième moitié du problème, mais je ne suis pas sûr de savoir comment les réunir.

Vous cherchez une explication significative ... Je voudrais "Get It". :)

+0

votre exemple n'a pas de sens, quand vous faites AddRule(). CannotBeNull(). CannotBeBlank() vous dites que vous voulez ajouter ces règles à vos règles de validation et de les appliquer plus tard? –

+0

Oui, précisément. Je veux être en mesure d'utiliser AddRule suivi de n'importe quel nombre de méthodes chaînées qui appliquent la validation sur une propriété donnée de la classe. Mon défi est que je ne sais pas quoi faire à l'intérieur de "AddRule". Je sais que je dois persister dans le validateur, mais je ne sais pas comment faire ça? – ctorx

Répondre

5

La clé des interfaces fluides est que des méthodes telles que CannotBeNull() et CannotBeBlank() renvoient l'instance actuelle (c'est-à-dire). Si vous souhaitez que votre méthode AddRule soit "fluide", au lieu de retourner ValidationResult, vous devez renvoyer l'instance actuelle de IValidator. Vos méthodes d'extension doivent également renvoyer l'instance d'IValidator qu'elles étendent. Je pense que votre implémentation exacte devra peut-être être un peu plus complexe, et j'espère que l'exemple ci-dessous vous donnera un aperçu. Même règle générale, mais ... retour « ce » pour créer une interface fluide:

interface IValidator<T> 
{ 
    IValidatorRule<T, TProp> AddRule<TProp>(Func<T, TProp> property); 
} 

interface IValidatorRule<T> 
{ 
    T instance { get; } 
    string PropertyName { get; } 

    ValidationResult Apply(T instance); 
} 

public static IValidatorAugmentorExtensions 
{ 
    public static IValidatorRule<T> CannotBeNull(this IValidatorRule<T> rule) 
    { 
     // ... 

     return rule; 
    } 

    public static IValidatorRule<T> CannotBeBlank(this IValidatorRule<T> rule) 
    { 
     // ... 

     return rule; 
    } 
} 

Le ci-dessus pourrait être utilisé comme ceci:

public class PersonValidator: IValidator<Person> 
{ 
    public PersonValidator() 
    { 
     AddRule(p => p.FirstName).CannotBeNull().CannotBeEmpty(); 
     AddRule(p => p.LastName).CannotBeNull().CannotBeEmpty(); 
    }  

    public List<ValidationResult> Validate(Person p) 
    { 
     List<ValidationResult> results = new List<ValidationResult>(); 

     foreach (IValidatorRule<Person> rule in rules) // don't know where rules is, or what the AddRule method adds to...you'll need to figure that out 
     { 
      results = rule.Apply(p); 
     } 

     return results; 
    } 
} 

Alors que le montre ci-dessus comment créer une interface fluide , Je ne sais pas vraiment ce qu'il vous achète à long terme dans cette situation particulière. Pour la commodité d'une interface fluide qui semble être utilisée uniquement en interne pour les valideurs de béton, vous avez augmenté la complexité de votre code de manière significative, sans vraiment fournir une interface utile et fluide aux utilisateurs de vos validateurs. Je pense que vous obtiendriez plus de valeur en fournissant un cadre de validation fluide aux développeurs qui doivent effectuer la validation, plutôt que de fournir un cadre fluide pour la création de validateurs concrets.

+0

+1 c'est ce que j'allais écrire, mais vous m'avez battu. Donc j'ai pris une approche différente. –

+0

Ils ont besoin de clé ici, c'est ce qui se passe avec "Règles". À quoi ressemble la liste locale et comment est-elle utilisée? – ctorx

+0

La solution la plus simple serait de faire en sorte que Rules soit une liste locale >.La méthode AddRule devrait créer l'IValidatorRule et l'ajouter à cette collection, et les méthodes d'extension peuvent être utilisées pour modifier cette instance via l'interface courante. Tout cela mis à part, je dois souligner à nouveau que je pense que vous dépensez BEAUCOUP d'efforts pour un gain minime. Si vous voulez vraiment réaliser les avantages d'une interface fluide, je repenserais votre cadre de validation. Plutôt que de fournir des validateurs concrets (c'est-à-dire PersonValidator), fournissez une interface fluide pour ceux qui effectuent la validation. – jrista

1

La réponse de jrista est correcte. Juste pour une approche différente voici comment je l'ai accompli.

public class PersonValidator : IValidator<Person> 
    { 
     List<Func<Person,bool>> validationRules = new List<Func<Person,bool>>(); 

    public PersonValidator() 
    { 
     AddRule(p => IsNullOrEmpty(p.FirstName)).AddRule(p1 => CheckLength(p1.FirstName)); 
    } 

    PersonValidator AddRule(Func<Person,bool> rule) 
    { 
     this.validationRules.Add(rule); 
     return this; 
    } 

    private bool IsNullOrEmpty(String stringToCheck) 
    { 
     return String.IsNullOrEmpty(stringToCheck); 
    } 

    private bool CheckLength(String stringToCheck) 
    { 
     return (String.IsNullOrEmpty(stringToCheck) ? false : stringToCheck.Length < 3); 
    } 

    #region IValidator<Person> Members 

    public bool Validate(Person obj) 
    { 
     return validationRules.Select(x => x(obj)).All(result => result == false); 
    } 

    #endregion 
} 



     Person test = new Person() { FirstName = null }; 
     Person test1 = new Person() { FirstName = "St" }; 
     Person valid = new Person() { FirstName = "John" }; 

     PersonValidator validator = new PersonValidator(); 
     Console.WriteLine("{0} {1} {2}", validator.Validate(test), validator.Validate(test1), validator.Validate(valid)); 
+0

Je ne vois pas comment cet exemple pourrait faciliter cet usage ... AddRule (x => x.FirstName) .IsNullOrEmpty(); – ctorx

+1

ce ne serait pas parce que c'est une approche différente, je voulais juste finir mon code au lieu de simplement l'oublier seulement parce que quelqu'un d'autre a répondu avant moi. –

Questions connexes