2009-08-21 6 views
7

Istnieje wiele płynnych implementacji, które teraz działają z Lambdas, aby robić rzeczy całkiem zadbane. Chciałbym otoczyć mój mózg, aby móc zacząć tworzyć niektóre z tych rzeczy, ale muszę jeszcze znaleźć wytłumaczenie, które mój mózg rozumie.Lambda Func <> i Fluent

Rozważmy prosty przykład osoby 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 
    } 
} 

udało mi się dostać część to wszystko działa za pomocą metody na moim Walidatora jak to ...

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 może następnie tworzyć metody rozszerzające, które rozszerzają IValidator na potrzeby CannotBeNull i CannotBeEmpty.

Wygląda na to, że mam pierwszą połowę i drugą połowę problemu, ale nie jestem pewien, jak je połączyć.

Szukasz sensownego wyjaśnienia ... Chciałbym "dostać to". :)

+0

Twój przykład nie ma sensu, kiedy robisz AddRule(). CannotBeNull(). CannotBeBlank() mówisz chcesz dodać te zasady do swoich reguł sprawdzania poprawności i zastosować je później? –

+0

Tak, właśnie. Chcę móc używać AddRule, a następnie dowolnej liczby połączonych metod, które stosują sprawdzanie poprawności dla danej właściwości klasy. Moim wyzwaniem jest to, że nie wiem, co zrobić w "AddRule". Wiem, że muszę utrzymywać te w walidatorze, ale nie wiem jak to zrobić? – ctorx

Odpowiedz

5

Kluczem do płynnych interfejsów jest to, że metody takie jak CannotBeNull() i CannotBeBlank() zwracają bieżącą instancję (to jest to). Jeśli chcesz, aby Twoja metoda AddRule była "płynna", zamiast zwracania ValidationResult, musisz zwrócić bieżącą instancję programu IValidator. Twoje metody rozszerzenia będą również musiały zwrócić instancję IValidator, którą rozszerzają.

Myślę, że twoja dokładna implementacja może być nieco bardziej skomplikowana i mam nadzieję, że poniższy przykład zapewni pewien wgląd. Sama zasada ogólna, jednak ... powrót „to”, aby utworzyć biegle interfejs:

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

Powyższe może być używany tak:

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

Chociaż powyższy pokazuje, jak stworzyć interfejs biegle , Tak naprawdę nie wiem, co kupuje cię na dłuższą metę w tej konkretnej sytuacji. Dla wygody płynnego interfejsu, który wydaje się być wykorzystywany tylko wewnętrznie do konkretnych walidatorów, zwiększyłeś złożoność swojego kodu o sporą kwotę, nie zapewniając w rzeczywistości użytecznego, płynnego interfejsu dla użytkowników walidatorów. Sądzę, że zyskałbyś większą wartość, zapewniając programistom, którzy muszą przeprowadzić walidację, płynne ramy weryfikacji, zamiast zapewniać płynne ramy do tworzenia konkretnych walidatorów.

+0

+1 To właśnie miałem zamiar napisać, ale mnie do tego zmusiłeś. Więc zastosowałem inne podejście. –

+0

Kluczem tutaj jest to, co dzieje się z "Regułami". Jak wygląda lista lokalna i jak jest wykorzystywana? – ctorx

+0

Najprostszym rozwiązaniem byłoby ustawienie Reguły jako lokalnej Listy >.Metoda AddRule powinna utworzyć IValidatorRule i dodać ją do tej kolekcji, a metody rozszerzenia można wykorzystać do modyfikacji tej instancji za pomocą płynnego interfejsu. Odkładając to na bok, muszę jeszcze raz podkreślić, że wydaje mi się, że poświęcasz dużo wysiłku na bardzo niewielki zysk. Jeśli naprawdę chcesz osiągnąć korzyści płynące z płynnego interfejsu, przemyślałbym swoje ramy sprawdzania poprawności. Zamiast dostarczania konkretnych walidatorów (np. PersonValidator), zapewnia płynny interfejs dla osób dokonujących walidacji. – jrista

1

Odpowiedź jrista jest poprawna. Dla innego podejścia tutaj jest to, jak to zrobiłem.

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

Nie widzę, jak ten przykład ułatwiłby to użycie ... AddRule (x => x.FirstName) .IsNullOrEmpty(); – ctorx

+1

to nie dlatego, że jest to inne podejście, chciałem tylko dokończyć kod, zamiast po prostu zapomnieć o tym tylko dlatego, że ktoś inny odpowiedział przede mną. –