代码改变世界

青梅煮酒论C#:Specification pattern

2011-01-16 13:56  空逸云  阅读(3016)  评论(17编辑  收藏

Specification模式早在3个多月前,阅读一个开源博客的时候便接触到了.但并没多少深入.最近,抽空将其好好研究了一番,果然,其魅力的确能让你回味无穷,那现在,就让我们零距离接触Specification模式吧!

何为Specification?

Specification,中文有翻译规格,虽然有很多争论,但目前叫得最多的,还是规格模式,既然说到了规格,那其作用估计就能不言而喻了.

Specification模式的作用是构建可以自由组装的业务逻辑元素.

上面这句话可以完整的概括Specification的作用,但第一次理解或许会有些许困难,没关系,可以这么理解,Specification模式起作用的,是每个Specification类,每一个Specification类,可以看成一张"规格书",或者说是某种"规格约束",而通过这张"规格书"或者"规格约束",我们能得到我们所想要的这种"规格"的对象,并且,不同的规格是可以组合起来,形成新的规格.如果还不明白.好吧,您知道乐高积木吧?我们形象的把每个规格想象成一个不同的乐高模型,而不同的乐高模型组合在一起了,就形成了新的模型,以达到我们所需的结果.

specification-pattern-uml

如何实现Specification

了解了什么是Specification,那接下去,您肯定会问,该怎么实现?其实很简单,Specification模式里面只有一个方法,我们有了"规格书",下一步,当然就是从不同的对象中得到符合"规格书"的对象,那这个匹配的过程就形成了一个动作抽象.IsSatisfiedBy就是帮助你从"对象堆"中找到你所需"规格"的动作.如此.我们可以实现的Specification接口如下

    public interface ISpecification<T>
    {

        bool IsSatisfiedBy(T candidate);

    }

但是,一般的Specification都提供了几个基本的组合操作,例如Add,Or,Not,可以利用它们分别组合成一个新的Specification.经过初步改动,最后ISpecification如下

    public interface ISpecification<T>
    {

        bool IsSatisfiedBy(T candidate);

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

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

        ISpecification<T> Not();

    }

为了重用可复用逻辑,我们定义了一个抽象类CompositeSpecification

    public abstract class CompositeSpecification<T> : ISpecification<T>
    {

        public abstract bool IsSatisfiedBy(T candidate);

        public ISpecification<T> Add(ISpecification<T> other)
        {

            return new AddSpecification<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);

        }

    }

并定义了基本的几个"组合"类

    public class AddSpecification<T> : CompositeSpecification<T>
    {

        private ISpecification<T> one;

        private ISpecification<T> other;

        public AddSpecification(ISpecification<T> one, ISpecification<T> other)
        {

            this.one = one;

            this.other = other;

        }

        public override bool IsSatisfiedBy(T candidate)
        {

            return one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate);

        }

    }

    public class OrSpecification<T> : CompositeSpecification<T>
    {

        private ISpecification<T> one;

        private ISpecification<T> other;

        public OrSpecification(ISpecification<T> one, ISpecification<T> other)
        {

            this.one = one;

            this.other = other;

        }

        public override bool IsSatisfiedBy(T candidate)
        {

            return one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate);

        }

    }

    public class NotSpecification<T> : CompositeSpecification<T>
    {

        private ISpecification<T> one;

        public NotSpecification(ISpecification<T> one)
        {

            this.one = one;

        }

        public override bool IsSatisfiedBy(T candidate)
        {

            return !one.IsSatisfiedBy(candidate);

        }

    }

最后,我们定义我们所需要的规格,在这里,我们定义一个基数规格与正数规格

    public class OddSpecification : ComsptionSpecification<int>
    {

        public override bool IsSatisfiedBy(int candidate)
        {

            return candidate % 2 != 0;

        }

    }

    public class PlusSpecification : ComsptionSpecification<int>
    {

        public override bool IsSatisfiedBy(int candidate)
        {

            return candidate > 0;

        }

    }

现在,万事俱备,只欠东风.就差你来调用了.

        static void Main(string[] args)
        {

            var items = Enumerable.Range(-5, 10);

            ISpecification<int> oddSpec = new OddSpecification();

            var spec = MoreCandidate(oddSpec);

            foreach (var i in items.Where(it => spec.IsSatisfiedBy(it)))
            {

                Console.WriteLine(i);

            }

        }

        static ISpecification<int> MoreCandidate(ISpecification<int> spec)
        {

            ISpecification<int> plusSpec = new PlusSpecification();

            return spec.Or(plusSpec);

        }

这个逻辑还是十分简单的.我们先定义了一个基数规格,后面我们发现这个规格不足以得到我们所需要的,我们想OrWhere它是正数.于是,我们"组合"成了一个新的规格.结果

clip_image002

这里应该注意的是.在这里我们提供了一种OrWhere功能,这是在一条条件的基础上再匹配多另一条条件,有同学说直接Where(it=>it%2!==0||it>0)就可以了.必须指明的是,后者就变成了一个条件.你把两个条件合并写死成一个条件了.不是将其"组合"成一个新条件.就像你用乐高堆砌出一架跑车模型和直接浇铸出一架跑车模型的概念是完全不同的.

优雅实现

虽然我们完美的实现了Specification模式,但是,聪明的你应该能看到它的弊病了.就是传统的Specification模式实在是太"重量级"了,如果你想实现一个新的"规格",那么你必须需要新增一个新的Specification类.这样下来,最终我们的类库中必然堆积了许许多多的Specification类.既然如此,有没有什么方法可以让其的实现变得更加的轻易?答案是肯定的.我们再一次解析Specification模式,其重点是IsSatisfiedBy方法.遵守我们一贯的思路,改进必然是在重点实现处.于是,一种优雅的实现便诞生了.

    public interface ISpecification<T>
    {

        bool IsSatisfiedBy(T candidate);

    }

我们还是必须定义一个ISpecification接口.与前面不同的是,该接口里不再有Add,Or,Not等方法.为何?后面讲解.

    public class Specification<T> : ISpecification<T>
    {

        private Func<T, bool> m_isSatisfiedBy;

        public bool IsSatisfiedBy(T candidate)
        {

            return m_isSatisfiedBy(candidate);

        }

        public Specification(Func<T, bool> isSatisfiedBy)
        {

            this.m_isSatisfiedBy = isSatisfiedBy;

        }

    }

接下来,我们定义了Specification<T>类继承了ISpecification<T>接口,此处要注意的是.在其构造函数中.我们传入了一个委托,然后,将该委托赋给私有变量m_isSatisfiedBy,最后.IsSatisfiedBy方法只是简单的调用了该Func,这实际上了也是依赖了注入.只不过我们注入的是一种特殊的类,委托.如此一来,只要我们需要新的"规格"时,只需要传入相应不同委托便可以实现.那么传统实现中Add,Or,Not等组合实现该如何实现.在次我们利用C#3.5的特性--扩展方法.我们实现了一个扩展类,并将所有的"组合"方法变成扩展方法.

    public static class SpecificationExtensions
    {

        public static ISpecification<T> Add<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));

        }

    }

至于为什么要生成一个新的扩展类,并将方法提取出来.站在面向对象的角度上并不能说哪种更"面向对象",实际上,并没有纯粹意义上的"面向对象标准".只是我们习惯上会不知不觉把一些"良好的面向对象实践"当成"标准",开发者站在不同的位置上,看到的东西,细节必然不同.如此,"面向对象"的结果也就不同.老赵说得很对(个人观点),当我们执行Add,Or,Not等操作的时候,并不是对象本身去Add,Or,Not,一般比较而言,更多的我们不会拿自己与别人比较,这样未免有失公正,而是有个"第三方",由它来进行比较,拿"你"和"他"进行比较.这里,我们也是借鉴现实生活的抽象,完成类的抽象.至此,这个轻量级的实现已经完成.

        static void Main(string[] args)
        {

            var items = Enumerable.Range(-5, 10);

            ISpecification<int> oddSpec = new Specification<int>(it => it % 2 != 0);

            var spec = MoreCandidate(oddSpec);

            foreach (var i in items.Where(it => spec.IsSatisfiedBy(it)))
            {

                Console.WriteLine(i);

            }

        }

        static ISpecification<int> MoreCandidate(ISpecification<int> spec)
        {

            ISpecification<int> plusSpec = new Specification<int>(it => it > 0);

            return spec.Or(plusSpec);

        }

clip_image004

轻量级实现

即使前面的实现已经足够"轻量级",但对于懒人而言却往往还是不够的.那么是否还有更"懒"的实现方法?答案是肯定的.再一想一下,Specification模式的实现其实就是IsSatisfiedBy方法的实现.那么我们直接把IsSatisfiedBy提取出来,形成一种更"懒"的实现.聪明的你应该想到了.答案就是利用委托.我们新建了一个委托.

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

命名为Spec完全只是为了说明这是一个Specification,它接受一个参数,并返回一个布尔值.只不过这种实现是在太过于简单.简单到没有提供一定的约束.所以,必须遵守一种约定,这也是约定大于约束的一种实现.和第二种实现同理,我们还实现一个SpecificationExtension类.

    public static class SpecExtensitions
    {

        public static Spec<T> Add<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);

        }

    }

这样一来.我们就实现了一套"超轻量级"的Specification实现.

        static void Main(string[] args)
        {

            var items = Enumerable.Range(-5, 10);

            Spec<int> oddSpec = it => it % 2 != 0;

            var spec = MoreCandidate(oddSpec);

            foreach (var i in items.Where(it => spec(it)))
            {

                Console.WriteLine(i);

            }

        }

        static Spec<int> MoreCandidate(Spec<int> spec)
        {

            return spec.Or(it => it > 0);

        }

结语

Specification模式的应用能很大程度上减少我们的工作量,目前应用的比较多的是在LINQ2SQL上,查询数据时采用不同的Specification从而获取你所需要的数据.MVC下的Reposity+Specification能大大提高项目的扩展性.不过,世上没有万能的灵丹妙药,如一把双刃剑,能伤人,也能伤己.任何一种模式的滥用都会造成不良的影响.所以,只有该需要它的时候使用它才是最合理的.那什么时候该使用呢?当你发现你的条件实在是太过千变万化,当你需要OrWhere,当你.....(欢迎你留下你认为合适的时候,:-) )

扩展阅读

Specification模式

依赖注入(控制反转)

趣味编程:C#中Specification模式的实现

趣味编程:C#中Specification模式的实现(参考答案 - 上)

趣味编程:C#中Specification模式的实现(参考答案 - 下)