规约模式Specification Pattern
什么是规约模式
规约模式允许我们将一小块领域知识封装到一个单元中,即规约,然后可以在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的更多相关文章
- 规约模式(Specification Pattern)
		前期准备之规约模式(Specification Pattern) 一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题 ... 
- [.NET领域驱动设计实战系列]专题三:前期准备之规约模式(Specification Pattern)
		一.前言 在专题二中已经应用DDD和SOA的思想简单构建了一个网上书店的网站,接下来的专题中将会对该网站补充更多的DDD的内容.本专题作为一个准备专题,因为在后面一个专题中将会网上书店中的仓储实现引入 ... 
- 规约模式Specification的学习
		最近一直在看DDD开发 规约似乎用得很普遍. 但是还是理解不了.所以记录下学习的进度.- 规约(Specification)模式 目的:查询语句和查询条件的分离 写了一个关于规约的模拟小程序 cla ... 
- 规约模式(Specification Pattern)
		一.引言 最近在看一个项目的源码时(DDD),对里面的一些设计思想和设计思路有了一些疑问.当看到(Repository层)中使用了 spec.SatisfiedBy() 时,感觉有点懵.于是在项目中搜 ... 
- 规格模式(Specification Pattern)
		本文节选自<设计模式就该这样学> 1 规格模式的定义 规格模式(Specification Pattern)可以认为是组合模式的一种扩展.很多时候程序中的某些条件决定了业务逻辑,这些条件就 ... 
- step_by_step_ABP规约模式
		一段时间没有在github 上浏览ABP项目,几天前看到ABP新增规约模式,开始了解并学习文档 记录一下 Introduction 介绍 Specification pattern is a pa ... 
- 设计模式:规约模式(Specification-Pattern)
		"其实地上本没有路,走的人多了,也便成了路"--鲁迅<故乡> 这句话很好的描述了设计模式的由来.前辈们通过实践和总结,将优秀的编程思想沉淀成设计模式,为开发者提供了解决 ... 
- 生产环境下实践DDD中的规约模式
		最近的开发工作涉及到两个模块“任务”和“日周报”.关系是日周报消费任务,因为用户在写日周报的时候,需要按一定的规则筛选当前用户的任务,作为日周报的一部分提交.整个项目采用类似于Orchard那种平台加 ... 
- [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
		一.前言 在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里. 二.规约模式的引入 在第三专题我们已经详细介绍了 ... 
随机推荐
- Bzoj1917 [Ctsc2010]星际旅行
			Time Limit: 10 Sec Memory Limit: 259 MBSubmit: 185 Solved: 118 Description 公元3000年,地球联盟已经攻占了银河系内的N ... 
- bzoj1015: [JSOI2008]星球大战starwar 并查集+离线处理
			题目传送门 这道题可以改为离线处理 倒着找答案 这样删点就变成加点了 有了这个思想题目就很好写了哇 23333 #include<cstdio> #include<cstring&g ... 
- MongoDB 聚合(管道与表达式)
			MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果.有点类似sql语句中的 count(*). aggregate() 方法 MongoDB中 ... 
- 【 Zabbix 】 — 基础安装
			一.基础环境 (1)centos6.7 x64 (2)JDK1.8 and zabbix-2.4.8(JDK是为了后期可以监控tomcat) (3)虚拟机测试环境 二.安装LAMP环境 这里为了快速搭 ... 
- 怎样用css来美化一个html页面
			# 转载请留言联系 我们都知道html写出来的东西是一个文本内容,很单调.和我们平时刷网页看到的内容不一样.那普通的网页是怎样对html超文本进行装饰的呢?没错,就是CSS. css的基本语法 选择器 ... 
- 扩展 RequestHandlerBase
			RequestHandlerBase 实现接口SolrRequestHandler SearchHandler: 它的所有逻辑来自 搜索组件SearchComponents. handler配置中, ... 
- mySQL的存储过程详解
			mysql存储过程详解 1. 存储过程简介 我们常用的操作数据库语言SQL语句在执行的时候需要要先编译,然后执行,而存储过程(Stored Procedure)是一组为了完成特定功能的S ... 
- [BZOJ1475]方格取数 网络流 最小割
			1475: 方格取数 Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 1025 Solved: 512[Submit][Status][Discuss] ... 
- (十四)基于GTID的主从复制
			(1)GTID主从复制 1)环境介绍 /etc/redhat-release CentOS Linux release 7.3.1611 (Core) MySQL版本:5.7 mysql> se ... 
- (八)MySQL索引操作
			(1)准备环境 mysql> create table t1(id int,name varchar(50)); mysql> \d $$ mysql> create procedu ... 
