Lambda Func<> and Fluent

7

现在有很多使用Lambda实现的流畅(Fluent)接口,可以完成非常优雅的操作。我想理解它的原理以便开始创建一些这样的东西,但是我还没有找到我的大脑能够理解的解释。

考虑这个简单的人员验证器(Person 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
     }
}

我已经成功地使用我的验证器上的一种方法来部分或全部完成了这个工作...

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
}

我可以创建扩展方法,以扩展IValidator的CannotBeNull和CannotBeEmpty功能。因此,我似乎解决了问题的前半部分和后半部分,但我不确定如何将它们结合起来。希望能够得到一个有意义的解释...我想要“明白”。 :)

你的例子没有意义,当你执行AddRule().CannotBeNull().CannotBeBlank()时,你是想将这些规则添加到你的验证规则中并稍后应用它们吗? - Stan R.
是的,确切地说。我希望能够使用AddRule,然后跟随任意数量的链接方法,在类的某个属性上应用验证。我的挑战在于我不知道在"AddRule"内部该怎么做。我知道我需要将这些持久化到验证器中,但我不知道如何做到这一点? - ctorx
2个回答

5
流畅接口的关键在于 CannotBeNull() 和 CannotBeBlank() 等方法返回当前实例(即 this)。如果您希望 AddRule 方法具有“流畅”的特性,而不是返回 ValidationResult,则需要返回 IValidator 的当前实例。您的扩展方法也需要返回它们正在扩展的 IValidator 实例。
我认为您的确切实现可能需要更加复杂,希望下面的示例可以提供一些见解。然而,同样的基本规则... 返回 "this" 以创建流畅接口:
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;
    }
}

上述内容可以这样使用:
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;
    }
}

尽管以上演示了如何创建流畅的接口,但我并不确定在这种特定情况下它能为你带来什么好处。为了方便一个似乎仅在具体验证器内部使用的流畅接口,您增加了相当数量的代码复杂性,同时并没有真正为验证器的消费者提供有用的、流畅的接口。我认为,为需要执行验证的开发人员提供一个流畅的验证框架,而不是提供一个用于创建具体验证器的流畅框架,将会带来更大的价值。


+1,这正是我要写的,但你比我先了。所以我采取了不同的方法。 - Stan R.
这里的关键需要看看“规则”发生了什么。本地列表是什么样子,如何利用它? - ctorx
最简单的解决方案是将Rules作为本地List<IValidatorRule<Person>>。AddRule方法应该创建IValidatorRule并将其添加到该集合中,扩展方法可以通过流畅的接口修改该实例。除此之外,我需要再次强调,我认为你为了很少的收益而付出了很多努力。如果你真的想要实现流畅接口的好处,我建议重新思考你的验证框架。不要提供具体的验证器(例如PersonValidator),而是为那些进行验证的人提供流畅的接口。 - jrista

1
jrista的回答是正确的。这里提供另一种方法,我是如何完成它的。
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));

我不明白这个例子如何能够实现这种用法...AddRule(x => x.FirstName).IsNullOrEmpty(); - ctorx
1
它不会因为不同的方法而失效,我只是想完成我的代码,而不是因为别人先回答了而忘记它。 - Stan R.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接