Specification模式的作用是构建可以自由组装的业务逻辑元素。不过就上篇文章的示例来看,“标准”的Specification模式的实现还是比较麻烦的,简单的功能也需要较复杂的代码。不过,既然说是“标准”的方式,自然就是指可以在任意面向对象语言中使用的实现方式,不过我们使用的是C#,在实际开发过程中,我们可以利用C#如今的强大特性来实现出更容易使用,更轻量级的Specification模式。
当然,有利也有弊,在使用“标准”还是“轻量级”的问题上,还要根据你的需求来进行选择。
Specification模式的关键在于,Specification类有一个IsSatisifiedBy函数,用于校验某个对象是否满足该Specification所表示的条件。多个Specification对象可以组装起来,并生成新Specification对象,这便可以形成高度可定制的业务逻辑。从中可以看出,一个Specification对象的关键,其实就是一个IsSatisifiedBy方法的逻辑。每种对象,一段逻辑。每个对象的唯一关键,也就是这么一段逻辑。因此,我们完全可以构造这么一个“通用”的类型,允许外界将这段逻辑通过构造函数“注入”到Specification对象中:
public class Specification: ISpecification { private Func bool> m_isSatisfiedBy; public Specification(Func bool> isSatisfiedBy) { this.m_isSatisfiedBy = isSatisfiedBy; } public bool IsSatisfiedBy(T candidate) { return this.m_isSatisfiedBy(candidate); } }
嗯嗯,这也是一种依赖注入。在普通的面向对象语言中,承载一段逻辑的最小单元只能是“类”,只是我们说,某某类中的某某方法就是我们需要的逻辑。而在C#中,从最早开始就有“委托”这个东西可用来承载一段逻辑。与其为每种情况定义一个特定的Specification类,让那个Spcification类去访问外部资源(即建立依赖),不如我们将这个类中唯一需要的逻辑给准备好,各种依赖直接通过委托由编译器自动保留,然后直接注入到一个“通用”的类中。很关键的是,这样在编程方面也非常容易。
至于原本ISpecification
public static class SpecificationExtensions { public static ISpecificationAnd ( this ISpecification one, ISpecification other) { return new Specification (candidate => one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate)); } public static ISpecification Or ( this ISpecification one, ISpecification other) { return new Specification (candidate => one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate)); } public static ISpecification Not (this ISpecification one) { return new Specification (candidate => !one.IsSatisfiedBy(candidate)); } }
此外,使用扩展方法的好处在于,如果我们想要加一个逻辑运算(如“异或”),那么是不需要修改接口的。修改接口是一件劳民伤财的事情。
至此,我们使用Specification对象就容易多了,因为不需要为每段逻辑创建一个独立的ISpecification
public delegate bool Spec(T candicate);
当然,您也可以直接使用Func
public static class SpecExtensions { public static SpecAnd (this Spec one, Spec other) { return candidate => one(candidate) && other(candidate); } public static Spec Or (this Spec one, Spec other) { return candidate => one(candidate) || other(candidate); } public static Spec Not (this Spec one) { return candidate => !one(candidate); } }
用它来编写上次的示例便容易多了:
static Spec<int> MorePredicate(Spec<int> original) { return original.Or(i => i > 0); } static void Main(string[] args) { var array = Enumerable.Range(-5, 10).ToArray(); var oddSpec = new Spec<int>(i => i % 2 == 1); var oddAndPositiveSpec = MorePredicate(oddSpec); foreach (var item in array.Where(i => oddAndPositiveSpec(i))) { Console.WriteLine(item); } }
由于有C#的扩展方法和委托,在C#中使用Specification模式比之前要容易许多。不过,在某些时候,我们可能还是需要老老实实按照标准来做。创建独立的Specification对象的好处是在一个单独的地方内聚地封装了一段逻辑,因此适合较集中,较“重”的逻辑,而“委托”则适合轻便的实现。委托的另一个优势是使用方便,但它的缺点便是难以“静态表示”。如果您在使用Specification模式时,需要根据外部配置来决定进行何种组装,那么可能只有为每种逻辑创建独立的Specification对象了。此外,使用委托还有一个“小缺点”,即它可能会“不自觉”地提升对象的生命周期,可能会形成一些延迟方面的陷阱。
当然,我并不是说独立Specification对象就不会造成生命周期延长——只要功能实现一样,各方面也应该是相同的。只不过独立的Specificaiton对象给人一种“正式”而“隆重”的感觉,容易让人警觉,因而缓解了这方面问题。
不过还有一个问题我们还没有解决——我们现在组装的是委托或Specification对象,但如果我们需要组装一个表达式树,组装完毕后交给如LINQ to SQL使用,又该怎么做呢?我们的“下”便会设法解决这个问题。