什么是规约模式

规约模式允许我们将一小块领域知识封装到一个单元中,即规约,然后可以在code base中对其进行复用。

它可以用来解决在查询中泛滥着GetBySomething方法的问题,以及对查询条件的组合和复用。

举个例子

public class Movie : Entity
{
public string Name { get; }
public DateTime ReleaseDate { get; }
public MpaaRating MpaaRating { get; }
public string Genre { get; }
public double Rating { get; }
}
public enum MpaaRating
{
G,
PG13,
R
}

这样如果按不同的条件来查询,就会出现如下代码

public class MovieRepository
{
public IReadOnlyList<Movie> GetByReleaseDate(DateTime maxReleaseDate) { } public IReadOnlyList<Movie> GetByRating(double minRating) { } public IReadOnlyList<Movie> GetByGenre(string genre) { }
}

如果我们想要根据多个条件来查询情况就会变得稍微负责一点,如下

public class MovieRepository
{
public IReadOnlyList<Movie> Find(
DateTime? maxReleaseDate = null,
double minRating = 0,
string genre = null)
{
/* … */
}
}

而且有时,我们需要在内存中筛选数据,有时我们需要在sql中筛选数据,这样就会出现如下两种写法

//内存中筛选数据
public Result BuyChildTicket(int movieId)
{
Movie movie = _repository.GetById(movieId); if (movie.MpaaRating != MpaaRating.G)
return Error(“The movie is not eligible for children”); return Ok();
}
//数据库中筛选数据
public class MovieRepository
{
public IReadOnlyList<Movie> FindMoviesForChildren()
{
return db
.Where(x => x.MpaaRating == MpaaRating.G)
.ToList();
}
}

这样子就违反了DRY原则,因为领域相关的信息(儿童电影)就出现在了两个位置。此时,我们可以通过规约模式来解决该问题。我们引入一个新的类来去甄别不同类型的电影。

这样不仅移除了重复的领域信息,还可以组合多个规约。

规约模式可以在如下三个场景使用

  • 查询数据库中的内容
  • 查询内存中的对象数据
  • 创建满足特定条件的实例

原始实现#

首先给出最原始的实现方式。这种方式依赖于c#的expression。如下(这也是目前我在项目直接想到使用的方式)

// Controller
public void SomeMethod()
{
Expression<Func<Movie, bool>> expression = m => m.MpaaRating == MpaaRating.G;
bool isOk = expression.Compile().Invoke(movie); // Exercising a single movie
var movies = _repository.Find(expression); // Getting a list of movies
//如上可以直接简写为如下一句代码
movies = _repository.Find(m => m.MpaaRating == MpaaRating.G);
} // Repository
public IReadOnlyList<Movie> Find(Expression<Func<Movie, bool>> expression)
{
return db
.Where(expression)
.ToList();
}

这种方式,解决了GetBySomething的情况,但是领域信息是不能复用的。m => m.MpaaRating == MpaaRating.G这样的代码可能会在应用中被复制多次。

一种改进的实现方式,是使用泛型规约类,如下。

public class GenericSpecification<T>
{
public Expression<Func<T, bool>> Expression { get; } public GenericSpecification(Expression<Func<T, bool>> expression)
{
Expression = expression;
} public bool IsSatisfiedBy(T entity)
{
return Expression.Compile().Invoke(entity);
}
} public void SomeMethod()
{
var specification = new GenericSpecification<Movie>(
m => m.MpaaRating == MpaaRating.G);
bool isOk = specification.IsSatisfiedBy(movie);
var movies = _repository.Find(specification);
}
public IReadOnlyList<Movie> Find(GenericSpecification<Movie> specification)
{
return db
.Where(specification.Expression)
.ToList();
}

这里呢,问题依然如上,仍旧没解决m => m.MpaaRating == MpaaRating.G不方便复用。

强类型规约#

这种方式,我们将领域信息硬编码进规约内,外部不能或者不太可能改变。

public abstract class Specification<T>
{
public abstract Expression<Func<T, bool>> ToExpression(); public bool IsSatisfiedBy(T entity)
{
Func<T, bool> predicate = ToExpression().Compile();
return predicate(entity);
}
} public class MpaaRatingAtMostSpecification : Specification<Movie>
{
private readonly MpaaRating _rating; public MpaaRatingAtMostSpecification(MpaaRating rating)
{
_rating = rating;
} public override Expression<Func<Movie, bool>> ToExpression()
{
return movie => movie.MpaaRating <= _rating;
}
}

这样我们使得领域信息更容易复用,而且在创建其他规约时,也不需要重复原来的规约。而且非常方便对规约进行组合,如And、Or和Not。如下

public abstract class Specification<T>
{
public Specification<T> And(Specification<T> specification)
{
return new AndSpecification<T>(this, specification);
} // And also Or and Not methods
} public class AndSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right; public AndSpecification(Specification<T> left, Specification<T> right)
{
_right = right;
_left = left;
} public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> leftExpression = _left.ToExpression();
Expression<Func<T, bool>> rightExpression = _right.ToExpression(); BinaryExpression andExpression = Expression.AndAlso(
leftExpression.Body, rightExpression.Body); return Expression.Lambda<Func<T, bool>>(
andExpression, leftExpression.Parameters.Single());
}
}
//如下扩展方法,是用来处理AndAlso中的表达式树参数的
public static class ExpressionExt
{
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}
}

这样,我们就可以组合规约了,如下

var gRating = new MpaaRatingAtMostSpecification(MpaaRating.G);
var goodMovie = new GoodMovieSpecification();
var repository = new MovieRepository();
IReadOnlyList<Movie> movies = repository.Find(gRating.And(goodMovie));

参考博客来源http://enterprisecraftsmanship.com/2016/02/08/specification-pattern-c-implementation/

规约模式Specification Pattern的更多相关文章

  1. 规约模式(Specification Pattern)

    前期准备之规约模式(Specification Pattern) 一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题 ...

  2. [.NET领域驱动设计实战系列]专题三:前期准备之规约模式(Specification Pattern)

    一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题作为一个准备专题,因为在后面一个专题中将会网上书店中的仓储实现引入 ...

  3. 规约模式Specification的学习

    最近一直在看DDD开发  规约似乎用得很普遍. 但是还是理解不了.所以记录下学习的进度.- 规约(Specification)模式 目的:查询语句和查询条件的分离 写了一个关于规约的模拟小程序 cla ...

  4. 规约模式(Specification Pattern)

    一.引言 最近在看一个项目的源码时(DDD),对里面的一些设计思想和设计思路有了一些疑问.当看到(Repository层)中使用了 spec.SatisfiedBy() 时,感觉有点懵.于是在项目中搜 ...

  5. 规格模式(Specification Pattern)

    本文节选自<设计模式就该这样学> 1 规格模式的定义 规格模式(Specification Pattern)可以认为是组合模式的一种扩展.很多时候程序中的某些条件决定了业务逻辑,这些条件就 ...

  6. step_by_step_ABP规约模式

    一段时间没有在github 上浏览ABP项目,几天前看到ABP新增规约模式,开始了解并学习文档   记录一下 Introduction 介绍 Specification pattern is a pa ...

  7. 设计模式:规约模式(Specification-Pattern)

    "其实地上本没有路,走的人多了,也便成了路"--鲁迅<故乡> 这句话很好的描述了设计模式的由来.前辈们通过实践和总结,将优秀的编程思想沉淀成设计模式,为开发者提供了解决 ...

  8. 生产环境下实践DDD中的规约模式

    最近的开发工作涉及到两个模块“任务”和“日周报”.关系是日周报消费任务,因为用户在写日周报的时候,需要按一定的规则筛选当前用户的任务,作为日周报的一部分提交.整个项目采用类似于Orchard那种平台加 ...

  9. [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现

    一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ...

随机推荐

  1. python 读 excel 模块: xlrd

    主要来自:[ python中使用xlrd.xlwt操作excel表格详解 ] 为了方便阅读, 我将原文两个模块拆分为两篇博文: [ python 读 excel 模块: xlrd ] [ python ...

  2. 鼠标DPI和液晶显示器分辨率的关系

    鼠标DPI和液晶显示器分辨率的关系 说起鼠标,有两个性能指标是我们不能忽略的.一是游戏玩家相当熟悉的扫描率(单位:Frames Per Second),二是我们今天要和大家探讨的鼠标的分辨率(单位:D ...

  3. ZigBee MAC层(上)

    1. 介绍 ZigBee MAC层,即IEEE 802.15.4 MAC层,这里主要介绍了802.15.4-2003版本 MAC层处理所有对物理无线信道的访问控制,并负责下面的任务 - 为协调器生成网 ...

  4. 常见协议基础知识总结--FTP协议

    FTP协议是一种基于客户端和服务器的文件传输协议,属于应用层协议,基于传输层的TCP协议: FTP主要分成主动模式和被动模式两种传输方式, 方式是相对服务器而言的,服务器主动发起数据连接即主动方式,使 ...

  5. linux下rm命令删除文件名中包含特殊字符的文件【转】

    转自:http://blog.itpub.net/143526/viewspace-1060083/ 1. 删除带“-”的文件名的方法 2. 删除包含其它特殊字符的文件 3. 删除系统打不出的乱码文件 ...

  6. [ kvm ] 进程的处理器亲和性和vCPU的绑定

    cpu调用进程或线程的方式: Linux内核的进程调度器根据自有的调度策略将系统中的一个进程调度到某个CPU上执行.一个进程在前一个执行时间是在cpuM上运行,而在后一个执行时间则是在cpuN上运行, ...

  7. python ajax post 数据

    简单的html <div> <input type="submit" id="tes" value="tes"> & ...

  8. Spring ClassPathXmlApplicationContext和FileSystemXmlApplicationContext读取配置文件的方法

    先说:ClassPathXmlApplicationContext 这个类,默认获取的是WEB-INF/classes/下的路径,也就是在myeclipse的src下的路径,所以用这个是获取不到WEB ...

  9. Linux下如何批量转码iconv

    来源:http://hi.baidu.com/curioz/blog/item/2555863514f9491d90ef390d.html 下载了不少文本txt,如verycd上的致纯书苑,解压看看是 ...

  10. crontab自动备份MySQL数据库并删除5天前备份

    1.创建备份文件夹 //备份数据库文件夹 mkdir /data/backmysql //crontab日志 mkdir /data/logs   2.创建脚本文件 db_user="xxx ...