文档章节

SpecificationPattern-规格模式

夏至如沫
 夏至如沫
发布于 2013/10/25 17:49
字数 1909
阅读 410
收藏 7

妈妈说我从小记性就不好,一切东西都是要记下来才行的。---原文是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

© 著作权归作者所有

共有 人打赏支持
夏至如沫
粉丝 11
博文 38
码字总数 23667
作品 0
郑州
后端工程师
ESS控制台发布新功能:创建多实例规格的伸缩配置

背景 原弹性伸缩ESS服务限定,生效的伸缩配置中只能对应一种实例规格,这样就会存在如果生效的配置中的实例规格的库存不足(高配实例规格通常更容易出现库存不足的情况)时, 用户配置好的伸...

charles晟
03/28
0
0
无线plc,无线开关量,无线io模块,wifi开关量,io控制卡,无线数据采集RTU模块

标题: 基于lora及wifi无线通信的无线PLC在物联网远程IO控制场合中的应用 标签: 无线plc,无线开关量,无线io模块,wifi开关量,io控制卡 文档介绍: 本文档描述lora和wifi无线通信方式在远程开...

zongkezhikong
07/05
0
0
无线plc,无线开关量,无线io模块,wifi开关量,io控制卡,无线数据采集RTU模块

标题: 基于lora及wifi无线通信的无线PLC在物联网远程IO控制场合中的应用 标签: 无线plc,无线开关量,无线io模块,wifi开关量,io控制卡 文档介绍: 本文档描述lora和wifi无线通信方式在远程开...

zongkezhikong
07/05
0
0
基于wifi无线PLC远程控制实现io开关量信号远程采集传输技术

深圳市综科智控科技开发有限公司是一家专注于生产与研发工业智能自动化设备及软件系统、工业物联网设备及软件系统的高新技术企业。 公司致力于为客户提供从前端数据采集、传感器接入、IO控制...

zongkezhikong
06/27
0
0
[功能优化] 包年包月实例和按量实例均已支持入门级变配企业级规格

目前ECS控制台在升降配-升级配置中,支持经典网络实例进行跨系列的迁移升级,从入门级实例升级到企业级实例规格。 现在,和也已支持从入门级变配到企业级规格。 什么是企业级实例?什么是入门...

郁苍
06/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

49.Nginx防盗链 访问控制 解析php相关 代理服务器

12.13 Nginx防盗链 12.14 Nginx访问控制 12.15 Nginx解析php相关配置(502的问题) 12.16 Nginx代理 扩展 502问题汇总 http://ask.apelearn.com/question/9109 location优先级 http://blog....

王鑫linux
58分钟前
0
0
Nginx防盗链、访问控制、解析php相关配置、Nginx代理

一、Nginx防盗链 1. 编辑虚拟主机配置文件 vim /usr/local/nginx/conf/vhost/test.com.conf 2. 在配置文件中添加如下的内容 { expires 7d; valid_referers none blocked server_names *.tes......

芬野de博客
今天
0
0
spring EL 和资源调用

资源调用 import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.PropertySource;import org.springframework.core.io.Resource;......

Canaan_
今天
1
0
memcached命令行、memcached数据导出和导入

一、memcached命令行 yum装telnet yum install telent 进入memcached telnet 127.0.0.1 11211 命令最后的2表示,两位字节,30表示过期时间(秒) 查看key1 get key1 删除:ctrl+删除键 二、m...

Zhouliang6
今天
0
0
Linux定时备份MySQL数据库

做项目有时候要备份数据库,手动备份太麻烦,所以找了一下定时备份数据库的方法 Linux里有一个 crontab 命令被用来提交和管理用户的需要周期性执行的任务,就像Windows里的定时任务一样,用这...

月夜中徘徊
今天
1
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部