上一篇文章中我们利用C#语言的特性实现了一种轻量级的Specification模式,它的关键在于抛弃了具体的Specification类型,而是使用一个委托对象代替唯一关键的IsSatisfiedBy方法逻辑。据我们分析,其优势之一在于使用简单,其劣势之一在于无法静态表示。但是它们还都是在处理“业务逻辑”,如果涉及到一个用于LINQ查询或其他地方的表达式树,则问题就不那么简单了——但也没有我们想象的那么复杂。

好,那么我们就把场景假想至LINQ上。LINQ与普通业务逻辑不同的地方在于,它不是用一个IsSatisfiedBy方法或一个委托对象用来表示判断逻辑,而是需要构造一个表达式树,一种数据结构。如果您还不清楚表达式树是什么,那么可以看一下脑袋的写的上手指南。这是.NET 3.5带来的重要概念,在4.0中又得到了重要发展,如果您要在.NET方面前进,这是一条必经之路。

And、Or和Not之间,最容易处理的便是Not方法,于是我们从这个地方下手,直接来看它的实现:

public static class SpecExprExtensions
{public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> one){var candidateExpr = one.Parameters[0];var body = Expression.Not(one.Body);return Expression.Lambda<Func<T, bool>>(body, candidateExpr);}
}

一个Expression<TDelegate>对象中主要有两部分内容,一是参数,二是表达式体(Body)。对于Not方法来说,我们只要获取它的参数表达式,再将它的Body外包一个Not表达式,便可以此构造一个新的表达式了。这部分逻辑非常简单,看了脑袋的文章,了解了表达式树的基本结构就能理解这里的含义。那么试验一下:

Expression<Func<int, bool>> f = i => i % 2 == 0;
f = f.Not();foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f))
{Console.WriteLine(i);
}

打印出来的自然是所有的奇数,即1、3、5。

而And和Or的处理上会有所麻烦,我们不能这样像Not一样简单处理:

public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{var candidateExpr = one.Parameters[0];var body = Expression.And(one.Body, another.Body);return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}

这么做虽然能够编译通过,但是在执行时便会出错。原因在于one和another两个表达式虽然都是同样的形式(Expression<Func<T, bool>>),但是它们的“参数”不是同一个对象。也就是说,one.Body和another.Body并没有公用一个ParameterExpression实例,于是我们无论采用哪个表达式的参数,在Expression.Lambda方法调用的时候,都会告诉您新的body中的某个参数对象并没有出现在参数列表中。

于是,我们如果要实现And和Or,做的第一件事情便是统一两个表达式树的参数,于是我们准备一个ExpressionVisitor:

internal class ParameterReplacer : ExpressionVisitor
{public ParameterReplacer(ParameterExpression paramExpr){this.ParameterExpression = paramExpr;}public ParameterExpression ParameterExpression { get; private set; }public Expression Replace(Expression expr){return this.Visit(expr);}protected override Expression VisitParameter(ParameterExpression p){return this.ParameterExpression;}
}

ExpressionVisitor几乎是处理表达式树这种数据结构的不二法门,它可以用于求值,变形(其实是生成新的结构,因为表达式树是immutable的数据结构)等各种操作。例如,解决表达式树的缓存时用它来求树的散列值或读写前缀树,快速计算表达式时用它来提取表达式树的参数,并将不同的表达式树“标准化”为相同的结构。

ExpressionVisitor基类的关键,就在于提供了遍历表达式树的标准方式,如果您直接继承这个类并调用Visit方法,那么最终返回的结果便是传入的Expression参数本身。但是,如果您覆盖的任意一个方法,返回了与传入时不同的对象,那么最终的结果就会是一个新的Expression对象。ExpressionVisitor类中的每个方法都负责一类表达式,也都都遵循了类似的原则:它们会递归地调用Visit方法,如果Visit返回新对象,那么它们也会构造新对象并返回。

ParameterReplacer类的作用是将一个表达式树里的所有ParameterExpression替换成我们指定的新对象,因此只需覆盖VisitParameter方法就可以了。有了它之后,And和Or方法的实现轻而易举:

public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{var candidateExpr = Expression.Parameter(typeof(T), "candidate");var parameterReplacer = new ParameterReplacer(candidateExpr);var left = parameterReplacer.Replace(one.Body);var right = parameterReplacer.Replace(another.Body);var body = Expression.And(left, right);return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{var candidateExpr = Expression.Parameter(typeof(T), "candidate");var parameterReplacer = new ParameterReplacer(candidateExpr);var left = parameterReplacer.Replace(one.Body);var right = parameterReplacer.Replace(another.Body);var body = Expression.Or(left, right);return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}

于是,我们最终构造得到的Expression<Func<T, bool>>对象便可以传入一个LINQ Provider,最终得到查询结果:

Expression<Func<int, bool>> f = i => i % 2 == 0;
f = f.Not().And(i => i % 3 == 0).Or(i => i % 4 == 0);foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f))
{Console.WriteLine(i);
}

输出的结果是3和4。

这种做法是非常有实用价值的。因为有了LINQ,因此许多朋友都会选择在数据访问层暴露一个这样的方法给上层调用:

class ProductDao
{public Product GetProduct(Expression<Func<Product, bool>> predicate){...}
}

但是您有没有想过这么做的缺点是什么呢?这么做的缺点便是“过于自由”。由于GetProduct方法只将参数限制为一个Expression<Func<Product, bool>>对象,因此在调用的时候,我们可以使用任意的形式进行传递。因此,外层完全有可能传入一个目前LINQ Provider不支持的表达式树形式,也完全有可能传入一个虽然支持,但会导致查询速度慢,影响项目整体性能的表达式树。前者要在运行时才抛出异常,而后者则引发的性能问题则更难发现。因此我认为,数据访问层不应该如此自由,它要做出限制。而限制的方式,便是使用Query Object模式,让GetProduct方法接受一个受限的Criteria对象:

public abstract class ProductCriteria
{internal ProductCriteria(Expression<Func<Product, bool>> query){this.Query = query;}public Expression<Func<Product, bool>> Query { get; private set; }
}class ProductDao
{public Product GetProduct(ProductCriteria predicate){...}
}

而在使用时,我们只提供有限的几种条件,如:

public class ProductIdEqCriteria : ProductCriteria
{public ProductIdEqCriteria(int id): base(p => p.ProductID == id){ }
}public class ProductViewRangeCriteria : ProductCriteria
{public ProductViewRangeCriteria(int min, int max): base(p => p.ViewCount > min && p.ViewCount < max){ }
}

再加上配套的扩展方法用于And,Or,Not,于是一切尽在掌握。现在再去瞅瞅原Query Object模式中复杂的实现,您是否会有一种满足感?

转载于:https://www.cnblogs.com/JeffreyZhao/archive/2009/09/29/specification-pattern-in-csharp-practice-answer-2.html

趣味编程:C#中Specification模式的实现(参考答案 - 下)相关推荐

  1. 趣味编程:函数式链表的快速排序(参考答案)

    之前我提出了一个"趣味编程",模仿Haskell的方式对一个链表进行快速排序.在那篇文章中我解释了Haskell列表的结构,并给出了ImmutableList的基础实现.快速排序的 ...

  2. 中职中职计算机英语试题,中职英语试卷及参考答案

    <中职英语试卷及参考答案>由会员分享,可在线阅读,更多相关<中职英语试卷及参考答案(5页珍藏版)>请在装配图网上搜索. 1.中职英语试卷及参考答案得 分阅卷人一.Transla ...

  3. 趣味编程:C#中Specification模式的实现

    今天有朋友在问了我这么一个问题:怎么实现OrWhere的功能?我猜测,他的意思是要实现这样的功能: static IEnumerable<int> MorePredicate(IEnume ...

  4. 趣味编程:从字符串中提取信息(参考答案 - 下)

    昨天我们观察了如何使用基于状态机的顺序解析方式来提取字符串中的信息,不过由于winter-cn的做法和我原始的想法不谋而合,但实现的更为清晰,因此我在不献丑的同时,又设法使用另外一种方式来解决这个问题 ...

  5. 企业会计准则2020版pdf_2020年下半年CATTI三级笔译中译英真题+参考答案+原文件汉英对照PDF版...

    当今世界,以互联网为代表的信息技术日新月异,引领了社会生产新变革,创造了人类生活新空间,拓展了国家治理新领域,极大提高了人类认识世界.改造世界的能力. Today, the rapid advance ...

  6. python积木式编程_【发现教育版亮点之美】3D One还能这么玩:“趣味编程”建模让你脑洞大开...

    原标题:[发现教育版亮点之美]3D One还能这么玩:"趣味编程"建模让你脑洞大开 "[有奖征文]发现3D One教育版亮点之美"教育版功能文章征集活动已经告一 ...

  7. 某计算机公司的库存管理,《管理系统中计算机应用》应用题数据流程图汇总题及参考答案...

    <管理系统中计算机应用>应用题及参考答案 --数据流程图1.教学管理的主要工作过程是:系办(公室)输入班级和教学时间,查看教学计划表,确定本学期教学任务:根据本学期教学任务,查看教师表,制 ...

  8. 趣味编程:从字符串中提取信息(参考答案 - 上)

    这次"趣味编程"的目的是解析字符串,从一个指定模式的字符串中提取信息.对于目前这个问题,解决方案有很多种,例如直接拆分,使用正则表达式,或是如现在本文这般按照顺序解析.总结果上来说 ...

  9. 趣味编程:从字符串中提取信息

    字符串解析是程序员工作中非常重要的一部分,也是非常考验编程能力的工作.基本上我在面试程序员的时候,一定会出一道编程题目作为考察的一方面,而这道题目有很大的可能性是做字符串的解析.例如,给出一个模式规则 ...

最新文章

  1. nginx配置websocket负载均衡
  2. 针对架构设计的几个痛点,我总结出的架构原则和模式
  3. 第 1 节: 1-文本自增演示HttpHandler不记忆状态
  4. 家庭扫地机器人竞争升级 带手臂提供移动服务或是未来方向
  5. 计算机网络_第7版_谢希仁_目录
  6. shell逐行读取文件拼接Sql语句并访问数据库
  7. 20120918-LIST类定义《数据结构与算法分析》
  8. MySql 手动执行主从备份
  9. eplan图纸怎么发给别人_手机拍的照片怎么打包发给别人
  10. vue json 编辑组件_内置为Vue组件的Visual JSON编辑器
  11. 基于matlab的qpsk与bpsk信号性能比较仿真,基于matlab的QPSK与BPSK信号性能比较仿真...
  12. File system specific implementation of LookupAndOpen [file] failed
  13. C#AE将当前地图导出为一张图片地图
  14. Tomcat启动异常:A child container failed during start 与 ClassNotFoundException解决方法
  15. 微信公众号的附件链接怎么弄
  16. STL容器迭代器的理解
  17. 大数据项目篇--项目架构图
  18. 网盘下载限速破解方法
  19. 伊人在线高清视频 index.php,《天元》“畅音阁”首发飞行技能视频
  20. 云课堂智慧职教答案python_智慧职教云课堂Python程序设计答案

热门文章

  1. 033-Unit 5 Standard I/O and Pipes
  2. python代码模块与模块之间空1行_Day006|Python语法基础
  3. nginx+php+memcache高速缓存openresty)
  4. 东风日产数据服务有限公司借力服务网格,实现7层流量精细化管控
  5. 阿里云MVP第14期全球发布:云时代2.0,遇见科技追梦者!
  6. 阿里巴巴向全社会开放黑科技:“泡在水里”的服务器
  7. 蓝桥练习题题解——作物杂交——Java
  8. 各行业2021薪酬报告来了,三大高薪行业令人羡慕
  9. preg_grep用法
  10. 由于没有远程桌面授权服务器可以提供许可证,远程回话被中断