设计模式:规约模式(Specification-Pattern)
“其实地上本没有路,走的人多了,也便成了路”——鲁迅《故乡》
这句话很好的描述了设计模式的由来。前辈们通过实践和总结,将优秀的编程思想沉淀成设计模式,为开发者提供了解决问题的思路。除此之外,设计模式还是开发者之间沟通的桥梁,是程序员的语言,比如我说这段代码用的是单例模式,你就知道它的基本实现和用法。因此非常有必要弄清楚常用的设计模式。
前辈们有很多优秀的设计模式文章和图书,而本系列是我的学习笔记,我会尽量清晰易懂的将自己知道的分享出来,如果有不准确的地方请及时指正 _
本文来讲解《规约模式(Specification-Pattern)》
什么是规约模式?
规约模式经常在DDD中使用,用来将业务规则(通常是隐式业务规则)封装成独立的逻辑单元,从而将隐式业务规则提炼为显示概念,并达到代码复用的目的。
讲理论就是很枯燥,比如上面这段定义,虽然说明白了什么是规约模式,但是又引入了两个概念:隐式业务规则和显示概念。太无趣了,我决定皮一下子……
- 什么是隐式业务规则?假如你开发了一个网站,你的目标用户是18岁以上人群,你懂的,当地的政策不允许18岁以下浏览。那么你该如何验证注册用户是否符合要求呢?
public ActionResult Register(UserRegisterInfo user){
if(user.Age < 18){
throw new Exception("Too young too simple...");
}
//todo:注册逻辑
}
在Register方法中if语句就是一条隐式业务规则。
当然这样写也能满足业务规则,但是改天新来一个叫王二的程序员,没闹明白为啥要加if判断、或者没闹明白为啥是18,本万物皆可盘的态度盘了这段代码,程序仍然照常运行,王二也很开心,但是业务完整性就被破坏了。因此就需要将隐式的业务规则提炼成现实概念。
- 什么是显示概念?显示概念跟隐式业务规则相对应,意味着我们要把上面代码中的if判断提炼出来了。
public ActionResult Register(UserRegisterInfo user){
var specification = new UserMustBeAdultSpecification();
if(!specification.IsSatisfiedBy(user)){
throw new Exception("Too young too simple...");
}
//todo:注册逻辑
}
class UserMustBeAdultSpecification {
private int adultAge;
public UserMustBeAdultSpecification(int adultAge = 18){
this.adultAge = adultAge;
}
public IsSatisfiedBy(UserRegisterInfo user){
return user.Age > this.adultAge;
}
}
我们把if判断提炼成一个显示概念,用来确认用户必须是成人。这样王二来了以后,也不至于揣着明白装糊涂。
为什么需要规约模式?
这里先说一下为什么需要把隐式业务规则转变为显示概念?通常我们的业务规则不会仅仅验证一下年龄这么简单,例如订单提交,你可能需要验证用户账号是否可用、订单商品的库存是否满足预定量、配送地址是否完整……如果仅仅是通过一连串的if判断,那就真的太不利于维护了,并且if嵌套的多了代码难于理解,不好说明白具体意图。因此需要将隐式业务规则转换成显示概念,这也是DDD的要求。
如果上面的例子还不能很好的打动你,我们再举一个栗子。
你的网站不仅仅需要注册吧,它可能还有更新用户信息的功能,更新的时候我们仍然需要确认用户必须是成人,看吧,提炼的显示概念再一次派上用场,达到了代码复用的目的,这样就满足了DRY的要求。
另外,规约模式还有一个更加常用的场景,就是进行数据查询,继续往下看……
如何实现规约模式?
规约模式要求我们每个规约都要有一个bool IsSatisfiedBy(model)方法,用来验证模型是否满足规约要求,我们上面的例子就是典型的规约类,但是没有进行任何抽象。
规约的另一个更常用的用途是进行数据筛选,而我们的筛选条件通常是复杂的,因此规约还要实现链式操作。因此需要进行抽象,到达操作一致的目的。
以下代码来自codeproject,不喜勿喷,熟悉的直接绕道最后一段。
定义接口:
public interface ISpecification<T> {
bool IsSatisfiedBy(T o);
ISpecification<T> And(ISpecification<T> specification);
ISpecification<T> Or(ISpecification<T> specification);
ISpecification<T> Not(ISpecification<T> specification);
}
每个规约实现四个方法:IsSatisfiedBy()、And()、Or()、Not()。IsSatisfiedBy()方法主要实现业务规则,而其它三个则用来将复合业务规则连在一起。
来看它的抽象实现:
public abstract class CompositeSpecification<T> : ISpecification<T>
{
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);
}
}
对于所有复合规约来说,And()、Or()、Not()方法都是相同的,只有IsSatisfiedBy()方法会有区别。接来下看一下链式规约的实现,分别对应And()、Or()、Not()方法:
public class AndSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> leftSpecification;
ISpecification<T> rightSpecification;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
this.leftSpecification = left;
this.rightSpecification = right;
}
public override bool IsSatisfiedBy(T o) {
return this.leftSpecification.IsSatisfiedBy(o)
&& this.rightSpecification.IsSatisfiedBy(o);
}
}
public class OrSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> leftSpecification;
ISpecification<T> rightSpecification;
public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
this.leftSpecification = left;
this.rightSpecification = right;
}
public override bool IsSatisfiedBy(T o) {
return this.leftSpecification.IsSatisfiedBy(o)
|| this.rightSpecification.IsSatisfiedBy(o);
}
}
public class NotSpecification<T> : CompositeSpecification<T>
{
ISpecification<T> specification;
public AndSpecification(ISpecification<T> specification) {
this.specification = specification;
}
public override bool IsSatisfiedBy(T o) {
return !this.specification.IsSatisfiedBy(o);
}
}
Linq表达式规约
当规约用于数据查询时,使用Linq表达式规约将非常有用。但是,这种方式与规约模式的初衷相悖,因为我们再一次把业务规则隐藏在了Linq表达式中。
基于表达式的规约
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);
}
}
看完代码你会发现,只要你愿意,表达式规约能够满足你几乎所有需求。因此说它是一种反模式,是违背初衷的用法。为啥还要列出来呢?对于查询来说太强大了……此处可以写一篇《论提升规约模式十倍生产力的方法》。
代码的用法
我们依然复制codeproject上面的代码:
class Program
{
static void Main(string[] args)
{
List<Mobile> mobiles = new List<Mobile> {
new Mobile(BrandName.Samsung, Type.Smart, 700),
new Mobile(BrandName.Apple, Type.Smart),
new Mobile(BrandName.Htc, Type.Basic),
new Mobile(BrandName.Samsung, Type.Basic) };
ISpecification<Mobile> samsungExpSpec =
new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Samsung);
ISpecification<Mobile> htcExpSpec =
new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Htc);
ISpecification<Mobile> SamsungHtcExpSpec = samsungExpSpec.Or(htcExpSpec);
ISpecification<Mobile> NoSamsungExpSpec =
new ExpressionSpecification<Mobile>(o => o.BrandName != BrandName.Samsung);
var samsungMobiles = mobiles.FindAll(o => samsungExpSpec.IsStatisfiedBy(o));
var htcMobiles = mobiles.FindAll(o => htcExpSpec.IsStatisfiedBy(o));
var samsungHtcMobiles = mobiles.FindAll(o => SamsungHtcExpSpec.IsStatisfiedBy(o));
var noSamsungMobiles = mobiles.FindAll(o => NoSamsungExpSpec.IsStatisfiedBy(o));
}
}
组合查询:
ISpecification<Mobile> complexSpec = (samsungExpSpec.Or(htcExpSpec)).And(brandExpSpec);
非Linq用法:
public class PremiumSpecification<T> : CompositeSpecification<T>
{
private int cost;
public PremiumSpecification(int cost) {
this.cost = cost;
}
public override bool IsSatisfiedBy(T o) {
return (o as Mobile).Cost >= this.cost;
}
}
组合用法:
ISpecification<Mobile> premiumSpecification = new PremiumSpecification<Mobile>(600);
ISpecification<Mobile> linqNonLinqExpSpec = NoSamsungExpSpec.And(premiumSpecification);
探讨:与CQRS的冲突,该如何选择?
在《CQRS vs Specification pattern》中,作者指出,规约模式提倡将验证和查询复用同一个逻辑单元,而在CQRS中,验证是在Command中的逻辑,查询是在Query中的逻辑,CQRS提倡命令和查询进行分离,从而构建低耦合的系统。
那么该如何选择呢?是选择SRP还是放弃DRY?
参考资料
- https://en.wikipedia.org/wiki/Specification_pattern
- https://www.codeproject.com/Articles/670115/Specification-pattern-in-Csharp
- https://enterprisecraftsmanship.com/posts/specification-pattern-c-implementation/
- https://enterprisecraftsmanship.com/posts/cqrs-vs-specification-pattern/
- https://my.oschina.net/HenuToater/blog/171378
设计模式:规约模式(Specification-Pattern)的更多相关文章
- 规约模式(Specification Pattern)
前期准备之规约模式(Specification Pattern) 一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题 ...
- [.NET领域驱动设计实战系列]专题三:前期准备之规约模式(Specification Pattern)
一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题作为一个准备专题,因为在后面一个专题中将会网上书店中的仓储实现引入 ...
- 规约模式Specification Pattern
什么是规约模式 规约模式允许我们将一小块领域知识封装到一个单元中,即规约,然后可以在code base中对其进行复用. 它可以用来解决在查询中泛滥着GetBySomething方法的问题,以及对查询条 ...
- 规约模式Specification的学习
最近一直在看DDD开发 规约似乎用得很普遍. 但是还是理解不了.所以记录下学习的进度.- 规约(Specification)模式 目的:查询语句和查询条件的分离 写了一个关于规约的模拟小程序 cla ...
- 浅谈设计模式--组合模式(Composite Pattern)
组合模式(Composite Pattern) 组合模式,有时候又叫部分-整体结构(part-whole hierarchy),使得用户对单个对象和对一组对象的使用具有一致性.简单来说,就是可以像使用 ...
- 转:设计模式-----桥接模式(Bridge Pattern)
转自:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html 记得看原始链接的评论. 学习设计模式也有一段时间了,今天就把我整理 ...
- 设计模式 - 策略模式(Strategy Pattern) 具体解释
策略模式(Strategy Pattern) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26577879 本文版权全 ...
- 设计模式 - 命令模式(command pattern) 多命令 具体解释
命令模式(command pattern) 多命令 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考命令模式: http://blog.csdn.ne ...
- 设计模式 - 命令模式(command pattern) 具体解释
命令模式(command pattern) 详细解释 本文地址: http://blog.csdn.net/caroline_wendy 命令模式(command pattern) : 将请求封装成对 ...
- 设计模式 - 命令模式(command pattern) 宏命令(macro command) 具体解释
命令模式(command pattern) 宏命令(macro command) 具体解释 本文地址: http://blog.csdn.net/caroline_wendy 參考: 命名模式(撤销) ...
随机推荐
- vue—自定义指令
今日分享—自定义指令 需要学习的点: modifiers属性的具体实例就是v-on:click.stop=”handClick” 一样,为指令添加一个修饰符. 全局指令:新建一个newDir.js i ...
- 一篇很好的学习查看Java源代码的文章
目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 6) 调整数组容量 ...
- 解决Debina系统自动更新软件包的问题
不知从何时开始,我的电脑每天开机连接上网络之后,不断的在下载数据,状态栏显示网速达到每秒1到2兆.开始我还不太在意,不过后来由于带宽全部被这种莫名其奥妙的下载占据了,我连网页都无否正常浏览了,所以我决 ...
- IDEA启动tomcat报java.net.SocketExceptionsocket closed
IDEA启动tomcat报java.net.SocketException:socket closed.如图所示 解决方法:打开任务管理器,检查有没有java.exe进程. 关闭了重新启动就好了 ...
- golang数据结构之循环链表
循环链表还是挺有难度的: 向链表中插入第一条数据的时候如何进行初始化. 删除循环链表中的数据时要考虑多种情况. 详情在代码中一一说明. 目录结构如下: circleLink.go package li ...
- [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)
一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...
- 程序计数器(PC)、堆栈指针(SP)与函数调用过程
PC(program counter)是CPU中用于存放下一条指令地址的寄存器,SP为堆栈指针.下面将介绍函数调用过程中CPU对PC和SP这两个寄存器的操作. 假设有如下函数Fun Fun() { … ...
- RS485与RS232
以下内容为结合视频,加上自述对其理解. 信息在传输线上通过电压信息进行传输,一个字节的数据有8位. 当传输一个字节的信息时,通信方式有串行通信与并行通信,在这两种通信方式之中,RS485是并行通信,R ...
- redis数据类型--set
set是String的一个无序集合,最大存储量2^32-1(大概40多亿) 1.操作命令:(xxx可以是任意字符串) sadd xxx a b c d e (添加一个或多个) smembers xxx ...
- vue 各种打包坑
1,报错 Refused to load the image 'http://localhost:8080/favicon.ico' because it violates the following ...