SpecificationPattern-规格模式

妈妈说我从小记性就不好,一切东西都是要记下来才行的。---原文是CodeProject 上的 Specification pattern in C#,觉得很不错,自己拿出来消化下跟大家分享。 首先什么是规格模式,百度了一下,竟然百科里没有,那就把原著里搬出来用吧。规格模式,根据维基百科上的解释,是一种特殊的软件设计模式,可以使用布尔逻辑将业务规则组合在一起构成新的业务规则。简单地说,每一粒的业务规则都是独立的并且基于单一职责的原则(SRP),我们可以使用加、减、非等简单逻辑将业务规则连接成为新的复合规则。这些业务规则就是我们这里说的规格。 曾经做个一个很脆的项目,是布艺加工行业的,比如现在有一个窗帘上用的布花,会有各种高端、大气、上档次的种类,当然每一种类都是要满足这个档次的各种规格要求。比如,普通屌丝家的布花也就规定下大小,形状,颜色而已,高端人士就有新的规格标准,土豪家的布花一定要大,一定要镀金,一定要有小的布花装饰在上面,等等云云。其实当初写代码的时候就是各种 && 和 && 加在一起验证是否合格的。现在想起来规格模式应该会适用。然后就可以开始尝试构建个模式了。当然为了省力,原文是用手机做示例,我肯定只负责粘代码了。

1.规格接口(粒度)

<!-- lang: c# -->
/// <summary>
/// 规格接口【表示最小粒度的单位】
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ISpecification<T>
{
    /// <summary>
    /// 规格的自描述(是否符合规格定义)
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    bool IsSatisfiedBy(T o);
    /// <summary>
    /// 规格可以被用来复合形成行的复杂规格
    /// </summary>
    /// <param name="specification"></param>
    /// <returns></returns>
    ISpecification<T> And(ISpecification<T> specification);
    ISpecification<T> Or(ISpecification<T> specification);
    ISpecification<T> Not(ISpecification<T> specification);
}

这个很好理解,本着接口是万能的原则,就把规格的各种基本操作定义在接口中。

2.抽象的规格基类

<!-- lang: c# -->
/// <summary>
/// 可以被复合的规则的抽象类
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class CompositeSpecification<T> :ISpecification<T>
{
    /// <summary>
    /// 是否符合规则【指定为子类必须实现的抽象方法】
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    public abstract bool IsSatisfiedBy(T o);


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

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

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

无论如何这些规格还是业务规则也好,最后都应该是可以一眼就看明白,并且我们可以直接重复使用的东西,所以应该把规格变为可以实例化的类,然后就应当实现接口中的全部方法了。本来读到这里的时候以为 AndSpecificationOrSpecificationNotSpecification 只是方法的重载,以便于阅读和封装调用。然后就发现在抽象基类里根本找不到,才注意到 new 关键字,原来竟然是新的类型。

<!-- lang: c# -->
 /// <summary>
/// 规格的逻辑加法
/// </summary>
/// <typeparam name="T"></typeparam>
public class AndSpecification<T> : CompositeSpecification<T>
{
    /// <summary>
    /// 加运算左参数
    /// </summary>
    ISpecification<T> leftSpecification;
    /// <summary>
    /// 加运算右参数
    /// </summary>
    ISpecification<T> rightSpecification;
    /// <summary>
    /// 加运算
    /// </summary>
    /// <param name="left"></param>
    /// <param name="right"></param>
    public AndSpecification(ISpecification<T> left, ISpecification<T> right)
    {
        this.leftSpecification = left;
        this.rightSpecification = right;
    }
    /// <summary>
    /// 重新实现自描述
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    public override bool IsSatisfiedBy(T o)
    {
        return this.leftSpecification.IsSatisfiedBy(o)
            && this.rightSpecification.IsSatisfiedBy(o);
    }
}

楞了一下想想,本来组合加在一起的规格就应该是新的类型。那么现在规格有了,实现规格的方法也有了。那么就想像一下我们应该怎么用吧。假如现在我们有了这么多手机:

<!-- lang: c# -->
 List<Mobile> mobiles = new List<Mobile>()
        {
            new Mobile(BrandName.Apple,Type.Smart,5888),
            new Mobile(BrandName.MEIZU,Type.Smart,2400),
            new Mobile(BrandName.Nokia,Type.Basic,700),
            new Mobile(BrandName.Samsung,Type.Smart,3200),
            new Mobile(BrandName.MI,Type.Smart,1600),
            new Mobile(BrandName.Htc,Type.Smart,1200),
            new Mobile(BrandName.CoolPad,Type.Smart,999)
        };

然后定义一个新的规格标准(只要三星的手机):

<!-- lang: c# -->
ISpecification<Mobile> samsungExpSpc

但是 ISpecification 只是一个接口,怎么实例化呢,难道是实现一个继承于CompositeSpecification 的类吗?当然这是我自己太笨,或者不认真没有认真思考一开始就明确定义的反省结构,其实只需要使用工厂方法创建出这个实例就行。先看一下完整的创建代码吧:

<!-- lang: c# -->
ISpecification<Mobile> samsungExpSpc = 
            new ExpressionSpecification<Mobile>(p => p.BrandName == BrandName.Samsung);

第一次看的时候觉得很神奇,怎么就用Lamda 表达式就创建了新的规格,不是应该创建相应的子类吗?说实话直到开始码这篇日记的时候才明白,刚才上面 CompositeSpecification 的代码并不是规格的定义,而是定义规格可以进行什么样的运算的。规格定义其实已经被延迟到使用的时候了,这也才是使用泛型的真正用意吧。已经看出来了,ExpressionSpecification 就是工厂方法,

/// <summary>
/// 工厂类(可以使用Lamba表达式创建规格)
/// </summary>
/// <typeparam name="T"></typeparam>
public class ExpressionSpecification<T> : CompositeSpecification<T>
{
    private Func<T, bool> expression;
    public ExpressionSpecification(Func<T, bool> expression)
    {
        if (expression == null)
            throw new ArgumentNullException();
        else
            this.expression = expression;
    }

    public override bool IsSatisfiedBy(T o)
    {
       // 使用传入的函数回调验证是否符合规格要求(这是让我觉得最神奇的地方)
        return this.expression(o);
    }
}

到这里应该可以看清楚结构状况了,就是通过 Lamda 表达式的形式创建我们规格。

<!-- lang: c# -->
private Func<T, bool> expression;

在创建之后就可以自由的使用了,各种复杂的规格定义也可以组合使用了。

<!-- lang: c# -->
       // 三星品牌
        ISpecification<Mobile> samsungExpSpc = 
            new ExpressionSpecification<Mobile>(p => p.BrandName == BrandName.Samsung);
        // 魅族品牌
        ISpecification<Mobile> meizuExpSpc =
            new ExpressionSpecification<Mobile>(p => p.BrandName == BrandName.MEIZU);
        // 三星或者魅族
        ISpecification<Mobile> samsungAndMeiZu = samsungExpSpc.And(meizuExpSpc);

好吧,终于消化完了,最后原著大神还很细心地提醒我们如果不使用 Lamda 表达式创建地话就无法适应在运算规则中的泛型结构,所以工厂返回结果时应当指明类型:

<!-- lang: c# -->
/// <summary>
/// 工厂方法(不使用Linq 表达式创建规格)
/// </summary>
/// <typeparam name="T"></typeparam>
public class PremiumSpecification<T> : CompositeSpecification<T>
{
    private int cost;
    /// <summary>
    /// 价格大于 传入参数 的规格
    /// </summary>
    /// <param name="cost"></param>
    public PremiumSpecification(int cost)
    {
        this.cost = cost;
    }
    /// <summary>
    /// 覆盖父类定义实现自描述
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    public override bool IsSatisfiedBy(T o)
    {
        // 为了适应特定的结构,在一些处理时时应当指明转化类型
        // 或者增加泛型的约束条件         where T : Mobile
        return (o as Mobile).Cost >= this.cost;
    }

最后附上博客园里设计模式的系列文章,向大神们致敬..

.NET设计模式系列文章

====================================================================

PS 最近正在做前期的设计测试,正好想到 规格模式 挺符合场景的,可能就可以正式搬进项目代码中了 再次 Mark 下 在此输入图片描述

虽然已经被你妮DOUBLE KILL(拒绝我俩次了)了,希望你安好

在此输入图片描述

===================================================================== 示例代码加入了窗体无边框和支持,有兴趣的可以 Down 下

示例代码:规格模式 - MyGril

转载于:https://my.oschina.net/HenuToater/blog/171378

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值