Specification模式(C#)

本文详细阐述了C#中Specification模式的实现,包括如何构建灵活的业务逻辑和利用扩展方法简化操作。通过引入OddSpecification和PositiveSpecification类,展示了如何组合不同条件以实现特定需求。此外,文章还探讨了使用委托替代Specification接口的优点,以及如何通过扩展方法和委托提高代码的可读性和可维护性。

C#中Specification模式的实现

今天有朋友在问了我这么一个问题:怎么实现OrWhere的功能?我猜测,他的意思是要实现这样的功能: 

static IEnumerable<int> MorePredicate(IEnumerable<int> source)
{
    return source

.OrWhere

(i => i > 0); // 或所有的正数
}

static void Main(string[] args)
{var array = Enumerable.Range(-5, 10).ToArray();
    var odd = array.Where(i => i % 2 != 0);  // 排除所有偶数
    var oddOrPositive = MorePredicate(odd);

    foreach (var i in oddOrPositive)
    {
        Console.WriteLine(i);
    }
}

以上代码应该输出-5,-3,-1,1,2,3,4。但显然,这段代码是无法编译通过,无法通过补充类库来实现的。因为在Main方法中的Where过后,已经排除了所有的偶数,于是在接下来的代码中是无法从新的序列中再次恢复元素的。

不过这个要求让我想起了Specification模式。Specification模式的作用是构建可以自由组装的业务逻辑元素。Specification类有一个IsSatisifiedBy函数,用于校验某个对象是否满足该Specification所表示的条件。多个Specification对象可以组装起来,并生成新Specification对象,这便可以形成高度可定制的业务逻辑。例如,我们可以使用依赖注入(控制反转)的方式来配置这个业务逻辑,以此保证系统的灵活性。

如果您点开上面那个Wikipedia的链接,就会发现这段描述大约是一个英译中的练习。抄完了“定义”,再来看看同样引自Wikipedia的UML图:

specification-pattern-uml

一般来说,Specification模式则意味着实现这样一个接口:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T candidate);

    ISpecification<T> And(ISpecification<T> other);

    ISpecification<T> Or(ISpecification<T> other);

    ISpecification<T> Not();
}

实现了ISpecification的对象则意味着是一个Specification,它可以通过与其他Specification对象的And,Or或对自身的取反来生成新的逻辑。为了方便这些“组合逻辑”的开发,我们还会准备一个抽象的CompositeSpecification类:

public abstract class CompositeSpecification<T> : ISpecification<T>
{
    public abstract bool IsSatisfiedBy(T candidate);

    public ISpecification<T> And(ISpecification<T> other)
    {
        return new AndSpecification<T>(this, other);
    }

    public ISpecification<T> Or(ISpecification<T> other)
    {
        return new OrSpecification<T>(this, other);
    }

    public ISpecification<T> Not()
    {
        return new NotSpecification<T>(this);
    }
}

CompositeSpecification提供了构建复合Specification的基础逻辑,它提供了And、Or和Not方法的实现,让其他Specification类只需要专注于IsSatisfiedBy方法的实现即可。例如,以下便是三种逻辑组合的具体实现:

public class AndSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_one;
    private ISpecification<T> m_other;

    public AndSpecification(ISpecification<T> x, ISpecification<T> y)
    {
        m_one = x;
        m_other = y;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return m_one.IsSatisfiedBy(candidate) && m_other.IsSatisfiedBy(candidate);
    }
}

public class OrSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_one;
    private ISpecification<T> m_other;

    public OrSpecification(ISpecification<T> x, ISpecification<T> y)
    {
        m_one = x;
        m_other = y;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return m_one.IsSatisfiedBy(candidate) || m_other.IsSatisfiedBy(candidate);
    }
}

public class NotSpecification<T> : CompositeSpecification<T>
{
    private ISpecification<T> m_wrapped;

    public NotSpecification(ISpecification<T> x)
    {
        m_wrapped = x;
    }

    public override bool IsSatisfiedBy(T candidate)
    {
        return !m_wrapped.IsSatisfiedBy(candidate);
    }
}

于是,我们便可以使用Specification模式来处理刚才那位朋友的问题。例如,首先他需要排除所有的偶数,那么我们不妨实现一个OddSpecification:

public class OddSpecification : CompositeSpecification<int>
{
    public override bool IsSatisfiedBy(int candidate)
    {
        return candidate % 2 != 0;
    }
}

自然,还有用于获得所有正数的Specification类:

public class PositiveSpecification : CompositeSpecification<int>
{
    public override bool IsSatisfiedBy(int candidate)
    {
        return candidate > 0;
    }
}

于是在使用时,我们会将其通过Or方法组合起来:

static ISpecification<int> MorePredicate(ISpecification<int> original)
{
    return original.Or(new PositiveSpecification());
}

static void Main(string[] args)
{var array = Enumerable.Range(-5, 10).ToArray();
    var oddSpec = new OddSpecification();
    var oddAndPositiveSpec = MorePredicate(oddSpec);

    foreach (var item in array.Where(i => oddAndPositiveSpec.IsSatisfiedBy(i)))
    {
        Console.WriteLine(item);
    }
}

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

当然,您也可以直接使用Func<T, bool>。我在这里创建Spec的目的,是因为我想“明确”这里其实是一个Specification,而不是一个普通的“接受T作为参数,返回bool的方法”。于是现在,我们便可以用这样的扩展方法来编写And,Or和Not:

public static class SpecExtensions
{
    public static Spec<T> And<T>(this Spec<T> one, Spec<T> other)
    {
        return candidate => one(candidate) && other(candidate);
    }

    public static Spec<T> Or<T>(this Spec<T> one, Spec<T> other)
    {
        return candidate => one(candidate) || other(candidate);
    }

    public static Spec<T> Not<T>(this Spec<T> 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对象给人一种“正式”而“隆重”的感觉,容易让人警觉,因而缓解了这方面问题。

以上内容摘自:http://www.cnblogs.com/jeffreyzhao/

 

 

转载于:https://www.cnblogs.com/jeriffe/articles/2278932.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值