Welcome

首页 / 软件开发 / C# / 趣味编程:C#中Specification模式的实现(参考答案 - 上)

趣味编程:C#中Specification模式的实现(参考答案 - 上)2010-06-05 博客园 赵劼Specification模式的作用是构建可以自由组装的业务逻辑元素。不过就上篇文章的示例来看,“标准”的Specification模式的实现还是比较麻烦的,简单的功能也需要较复杂的代码。不过,既然说是“标准”的方式,自然就是指可以在任意面向对象语言中使用的实现方式,不过我们使用的是C#,在实际开发过程中,我们可以利用C#如今的强大特性来实现出更容易使用,更轻量级的 Specification模式。

当然,有利也有弊,在使用“标准”还是“轻量级”的问题上,还要根据你的需求来进行选择。

Specification模式的关键在于,Specification类有一个IsSatisifiedBy函数,用于校验某个对象是否满足该 Specification所表示的条件。多个Specification对象可以组装起来,并生成新Specification对象,这便可以形成高度可定制的业务逻辑。从中可以看出,一个Specification对象的关键,其实就是一个IsSatisifiedBy方法的逻辑。每种对象,一段逻辑。每个对象的唯一关键,也就是这么一段逻辑。因此,我们完全可以构造这么一个“通用”的类型,允许外界将这段逻辑通过构造函数“注入”到 Specification对象中:

public class Specification<T> : ISpecification<T>
{
private Func<T, bool> m_isSatisfiedBy;

public Specification(Func<T, bool> isSatisfiedBy)
{
this.m_isSatisfiedBy = isSatisfiedBy;
}

public bool IsSatisfiedBy(T candidate)
{
return this.m_isSatisfiedBy(candidate);
}
}

嗯嗯,这也是一种依赖注入。在普通的面向对象语言中,承载一段逻辑的最小单元只能是“类”,只是我们说,某某类中的某某方法就是我们需要的逻辑。而在C#中,从最早开始就有“委托”这个东西可用来承载一段逻辑。与其为每种情况定义一个特定的Specification类,让那个 Spcification类去访问外部资源(即建立依赖),不如我们将这个类中唯一需要的逻辑给准备好,各种依赖直接通过委托由编译器自动保留,然后直接注入到一个“通用”的类中。很关键的是,这样在编程方面也非常容易。

至于原本ISpecification<T>中的And,Or,Not方法,我们可以将它们提取成扩展方法。有朋友说,既然有了扩展方法,那么对于一些不需要访问私有成员/状态的方法,都应该提取到实体的外部,避免“污染”实体。不过我不同意,在我看来,到底是用实例方法还是扩展方法,还是个根据职责和概念而一定的。我在这里打算使用扩展的目的,是因为And,Or,Not并非是一个Specification对象的逻辑,并不是一个Specification对象说,“我要去And另一个”,“我要去Or另一个”,或者“我要造……取反”。就好比二元运算符&&、||、或者+、-,左右两边的运算数字有主次之分吗?没有,它们是并列的。因此,我选择使用额外的扩展方法,而不是将这些职责交给某个Specification对象:

public static class SpecificationExtensions
{
public static ISpecification<T> And<T>(
this ISpecification<T> one, ISpecification<T> other)
{
return new Specification<T>(candidate =>
one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate));
}

public static ISpecification<T> Or<T>(
this ISpecification<T> one, ISpecification<T> other)
{
return new Specification<T>(candidate =>
one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate));
}

public static ISpecification<T> Not<T>(this ISpecification<T> one)
{
return new Specification<T>(candidate => !one.IsSatisfiedBy(candidate));
}
}

此外,使用扩展方法的好处在于,如果我们想要加一个逻辑运算(如“异或”),那么是不需要修改接口的。修改接口是一件劳民伤财的事情。

至此,我们使用Specification对象就容易多了,因为不需要为每段逻辑创建一个独立的 ISpecification<T>类型。但是,其实还有更简单的:直接使用委托。既然整个Specificaiton对象的逻辑可以使用一个委托直接表示,那为什么我们还需要一个“外壳”呢?不如直接使用这样的委托类型:

public delegate bool Spec<T>(T candicate);