妈妈说我从小记性就不好,一切东西都是要记下来才行的。---原文是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);
}
}
无论如何这些规格还是业务规则也好,最后都应该是可以一眼就看明白,并且我们可以直接重复使用的东西,所以应该把规格变为可以实例化的类,然后就应当实现接口中的全部方法了。本来读到这里的时候以为 AndSpecification
、OrSpecification
、NotSpecification
只是方法的重载,以便于阅读和封装调用。然后就发现在抽象基类里根本找不到,才注意到 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;
}
最后附上博客园里设计模式的系列文章,向大神们致敬..
====================================================================
PS 最近正在做前期的设计测试,正好想到 规格模式 挺符合场景的,可能就可以正式搬进项目代码中了 再次 Mark 下
虽然已经被你妮DOUBLE KILL(拒绝我俩次了)了,希望你安好
===================================================================== 示例代码加入了窗体无边框和支持,有兴趣的可以 Down 下
示例代码:规格模式 - MyGril