经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
设计模式:规约模式(Specification-Pattern) - 拓荒者-OTW
来源:cnblogs  作者:拓荒者-OTW  时间:2019/10/28 10:28:03  对本文有异议

“其实地上本没有路,走的人多了,也便成了路”——鲁迅《故乡》

这句话很好的描述了设计模式的由来。前辈们通过实践和总结,将优秀的编程思想沉淀成设计模式,为开发者提供了解决问题的思路。除此之外,设计模式还是开发者之间沟通的桥梁,是程序员的语言,比如我说这段代码用的是单例模式,你就知道它的基本实现和用法。因此非常有必要弄清楚常用的设计模式。

前辈们有很多优秀的设计模式文章和图书,而本系列是我的学习笔记,我会尽量清晰易懂的将自己知道的分享出来,如果有不准确的地方请及时指正 ^_^

本文来讲解《规约模式(Specification-Pattern)》

什么是规约模式?

规约模式经常在DDD中使用,用来将业务规则(通常是隐式业务规则)封装成独立的逻辑单元,从而将隐式业务规则提炼为显示概念,并达到代码复用的目的。

讲理论就是很枯燥,比如上面这段定义,虽然说明白了什么是规约模式,但是又引入了两个概念:隐式业务规则和显示概念。太无趣了,我决定皮一下子……

  • 什么是隐式业务规则?假如你开发了一个网站,你的目标用户是18岁以上人群,你懂的,当地的政策不允许18岁以下浏览。那么你该如何验证注册用户是否符合要求呢?
  1. public ActionResult Register(UserRegisterInfo user){
  2. if(user.Age < 18){
  3. throw new Exception("Too young too simple...");
  4. }
  5. //todo:注册逻辑
  6. }

在Register方法中if语句就是一条隐式业务规则。

当然这样写也能满足业务规则,但是改天新来一个叫王二的程序员,没闹明白为啥要加if判断、或者没闹明白为啥是18,本万物皆可盘的态度盘了这段代码,程序仍然照常运行,王二也很开心,但是业务完整性就被破坏了。因此就需要将隐式的业务规则提炼成现实概念。

  • 什么是显示概念?显示概念跟隐式业务规则相对应,意味着我们要把上面代码中的if判断提炼出来了。
  1. public ActionResult Register(UserRegisterInfo user){
  2. var specification = new UserMustBeAdultSpecification();
  3. if(!specification.IsSatisfiedBy(user)){
  4. throw new Exception("Too young too simple...");
  5. }
  6. //todo:注册逻辑
  7. }
  8. class UserMustBeAdultSpecification {
  9. private int adultAge;
  10. public UserMustBeAdultSpecification(int adultAge = 18){
  11. this.adultAge = adultAge;
  12. }
  13. public IsSatisfiedBy(UserRegisterInfo user){
  14. return user.Age > this.adultAge;
  15. }
  16. }

我们把if判断提炼成一个显示概念,用来确认用户必须是成人。这样王二来了以后,也不至于揣着明白装糊涂。

为什么需要规约模式?

这里先说一下为什么需要把隐式业务规则转变为显示概念?通常我们的业务规则不会仅仅验证一下年龄这么简单,例如订单提交,你可能需要验证用户账号是否可用、订单商品的库存是否满足预定量、配送地址是否完整……如果仅仅是通过一连串的if判断,那就真的太不利于维护了,并且if嵌套的多了代码难于理解,不好说明白具体意图。因此需要将隐式业务规则转换成显示概念,这也是DDD的要求。

如果上面的例子还不能很好的打动你,我们再举一个栗子。

你的网站不仅仅需要注册吧,它可能还有更新用户信息的功能,更新的时候我们仍然需要确认用户必须是成人,看吧,提炼的显示概念再一次派上用场,达到了代码复用的目的,这样就满足了DRY的要求。

另外,规约模式还有一个更加常用的场景,就是进行数据查询,继续往下看……

如何实现规约模式?

规约模式要求我们每个规约都要有一个bool IsSatisfiedBy(model)方法,用来验证模型是否满足规约要求,我们上面的例子就是典型的规约类,但是没有进行任何抽象。

规约的另一个更常用的用途是进行数据筛选,而我们的筛选条件通常是复杂的,因此规约还要实现链式操作。因此需要进行抽象,到达操作一致的目的。

以下代码来自codeproject,不喜勿喷,熟悉的直接绕道最后一段。

定义接口:

  1. public interface ISpecification<T> {
  2. bool IsSatisfiedBy(T o);
  3. ISpecification<T> And(ISpecification<T> specification);
  4. ISpecification<T> Or(ISpecification<T> specification);
  5. ISpecification<T> Not(ISpecification<T> specification);
  6. }

每个规约实现四个方法:IsSatisfiedBy()、And()、Or()、Not()。IsSatisfiedBy()方法主要实现业务规则,而其它三个则用来将复合业务规则连在一起。

来看它的抽象实现:

  1. public abstract class CompositeSpecification<T> : ISpecification<T>
  2. {
  3. public abstract bool IsSatisfiedBy(T o);
  4. public ISpecification<T> And(ISpecification<T> specification)
  5. {
  6. return new AndSpecification<T>(this, specification);
  7. }
  8. public ISpecification<T> Or(ISpecification<T> specification)
  9. {
  10. return new OrSpecification<T>(this, specification);
  11. }
  12. public ISpecification<T> Not(ISpecification<T> specification)
  13. {
  14. return new NotSpecification<T>(specification);
  15. }
  16. }

对于所有复合规约来说,And()、Or()、Not()方法都是相同的,只有IsSatisfiedBy()方法会有区别。接来下看一下链式规约的实现,分别对应And()、Or()、Not()方法:

  1. public class AndSpecification<T> : CompositeSpecification<T>
  2. {
  3. ISpecification<T> leftSpecification;
  4. ISpecification<T> rightSpecification;
  5. public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
  6. this.leftSpecification = left;
  7. this.rightSpecification = right;
  8. }
  9. public override bool IsSatisfiedBy(T o) {
  10. return this.leftSpecification.IsSatisfiedBy(o)
  11. && this.rightSpecification.IsSatisfiedBy(o);
  12. }
  13. }
  14. public class OrSpecification<T> : CompositeSpecification<T>
  15. {
  16. ISpecification<T> leftSpecification;
  17. ISpecification<T> rightSpecification;
  18. public AndSpecification(ISpecification<T> left, ISpecification<T> right) {
  19. this.leftSpecification = left;
  20. this.rightSpecification = right;
  21. }
  22. public override bool IsSatisfiedBy(T o) {
  23. return this.leftSpecification.IsSatisfiedBy(o)
  24. || this.rightSpecification.IsSatisfiedBy(o);
  25. }
  26. }
  27. public class NotSpecification<T> : CompositeSpecification<T>
  28. {
  29. ISpecification<T> specification;
  30. public AndSpecification(ISpecification<T> specification) {
  31. this.specification = specification;
  32. }
  33. public override bool IsSatisfiedBy(T o) {
  34. return !this.specification.IsSatisfiedBy(o);
  35. }
  36. }

Linq表达式规约

当规约用于数据查询时,使用Linq表达式规约将非常有用。但是,这种方式与规约模式的初衷相悖,因为我们再一次把业务规则隐藏在了Linq表达式中。

基于表达式的规约

  1. public class ExpressionSpecification<T> : CompositeSpecification<T> {
  2. private Func<T, bool> expression;
  3. public ExpressionSpecification(Func<T, bool> expression) {
  4. if (expression == null)
  5. throw new ArgumentNullException();
  6. else
  7. this.expression = expression;
  8. }
  9. public override bool IsSatisfiedBy(T o) {
  10. return this.expression(o);
  11. }
  12. }

看完代码你会发现,只要你愿意,表达式规约能够满足你几乎所有需求。因此说它是一种反模式,是违背初衷的用法。为啥还要列出来呢?对于查询来说太强大了……此处可以写一篇《论提升规约模式十倍生产力的方法》。

代码的用法

我们依然复制codeproject上面的代码:

  1. class Program
  2. {
  3. static void Main(string[] args)
  4. {
  5. List<Mobile> mobiles = new List<Mobile> {
  6. new Mobile(BrandName.Samsung, Type.Smart, 700),
  7. new Mobile(BrandName.Apple, Type.Smart),
  8. new Mobile(BrandName.Htc, Type.Basic),
  9. new Mobile(BrandName.Samsung, Type.Basic) };
  10. ISpecification<Mobile> samsungExpSpec =
  11. new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Samsung);
  12. ISpecification<Mobile> htcExpSpec =
  13. new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Htc);
  14. ISpecification<Mobile> SamsungHtcExpSpec = samsungExpSpec.Or(htcExpSpec);
  15. ISpecification<Mobile> NoSamsungExpSpec =
  16. new ExpressionSpecification<Mobile>(o => o.BrandName != BrandName.Samsung);
  17. var samsungMobiles = mobiles.FindAll(o => samsungExpSpec.IsStatisfiedBy(o));
  18. var htcMobiles = mobiles.FindAll(o => htcExpSpec.IsStatisfiedBy(o));
  19. var samsungHtcMobiles = mobiles.FindAll(o => SamsungHtcExpSpec.IsStatisfiedBy(o));
  20. var noSamsungMobiles = mobiles.FindAll(o => NoSamsungExpSpec.IsStatisfiedBy(o));
  21. }
  22. }

组合查询:

  1. ISpecification<Mobile> complexSpec = (samsungExpSpec.Or(htcExpSpec)).And(brandExpSpec);

非Linq用法:

  1. public class PremiumSpecification<T> : CompositeSpecification<T>
  2. {
  3. private int cost;
  4. public PremiumSpecification(int cost) {
  5. this.cost = cost;
  6. }
  7. public override bool IsSatisfiedBy(T o) {
  8. return (o as Mobile).Cost >= this.cost;
  9. }
  10. }

组合用法:

  1. ISpecification<Mobile> premiumSpecification = new PremiumSpecification<Mobile>(600);
  2. ISpecification<Mobile> linqNonLinqExpSpec = NoSamsungExpSpec.And(premiumSpecification);

探讨:与CQRS的冲突,该如何选择?

在《CQRS vs Specification pattern》中,作者指出,规约模式提倡将验证和查询复用同一个逻辑单元,而在CQRS中,验证是在Command中的逻辑,查询是在Query中的逻辑,CQRS提倡命令和查询进行分离,从而构建低耦合的系统。

那么该如何选择呢?是选择SRP还是放弃DRY?

参考资料

原文链接:http://www.cnblogs.com/youring2/p/Specification-Pattern.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号