规约

介绍

  规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则。(维基百科)。

  尤其是,它通常用来为实体或其他业务对象定义可复用的过滤器。

示例

  在这个部分,我们将看到规约模式的必要性。本部分是通用的,和ABP的实现没有必然的关系。

  假定,有一个服务方法,计算所有客户的总数量,如下所示:

public class CustomerManager
{
public int GetCustomerCount()
{
//TODO...
return ;
}
}

  你或许希望通过过滤器获取客户数量。例如,你有优质客户(拥有超过100,000美元的客户)或者想通过注册年份过滤客户。然后你创建了其他方法,如GetPremiumCustomerCount(),GetCustomerCountRegisteredYear(int year),GetPremiumCustomerCountRegisteredInYeaar(int year)还有更多。随着你有更多的标准,不可能为每种可能都创建一个组合。

  这个问题的解决方案之一就是规约模式。我们创建一个单独的方法,它有一个参数,我们把这个方法作为过滤器:

public class CustomerManager
{
private readonly IRepository<Customer> _customerRepository; public CustomerManager(IRepository<Customer> customerRepository)
{
_customerRepository = customerRepository;
} public int GetCustomerCount(ISpecification<Customer> spec)
{
var customers = _customerRepository.GetAllList(); var customerCount = ; foreach (var customer in customers)
{
if (spec.IsSatisfiedBy(customer))
{
customerCount++;
}
} return customerCount;
}
}

  从而,我们可以使用实现了ISpecification<Customer>接口的参数来获取任何对象,接口定义如下所示:

public interface ISpecification<T>
{
bool IsSatisfiedBy(T obj);
}

  我们可以调用IsSatisfiedBy方法来测试客户是否是有意向的。从而,我们可以使用不同的参数调用同样的方法GetCustomerCount,而不用改变方法本身。

  因为这个解决方案在理论上相当好,所以在C#中它应该被改善的更好。例如,从数据库里获取所有的客户并检查他们是否满足指定的规约/条件,这个操作是非常低效的。在下一部分,我们将看到ABP如何实现这个模式并克服了这个问题。

创建规范类

  ABP按如下方式 定义了ISpecification接口:

public interface ISpecification<T>
{
bool IsSatisfiedBy(T obj); Expression<Func<T, bool>> ToExpression();
}

  添加了ToExpression()方法,这个方法返回一个表达式,这样可以 更好的和IQueryable和表达式树集成。因此,我们可以轻松的传递规约到仓储,并在数据库级别应用过滤器。

  我们通常继承Specification<T>类,而不是直接实现ISpecification<T>接口。Specification类自动实现IsSatisfiedBy方法。所以,我们仅仅需要定义ToExpression。让我们创建一些规约类:

//Customers with $100,000+ balance are assumed as PREMIUM customers.
public class PremiumCustomerSpecification : Specification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return (customer) => (customer.Balance >= );
}
} //A parametric specification example.
public class CustomerRegistrationYearSpecification : Specification<Customer>
{
public int Year { get; } public CustomerRegistrationYearSpecification(int year)
{
Year = year;
} public override Expression<Func<Customer, bool>> ToExpression()
{
return (customer) => (customer.CreationYear == Year);
}
}

  如你所见,我们仅仅实现了简单的拉姆达表达式来定义规约。让我们使用这些规约获取客户的数量:

count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification());

使用仓储规约

  现在,我们优化CustomerManager在数据库中应用过滤器:

public class CustomerManager
{
private readonly IRepository<Customer> _customerRepository; public CustomerManager(IRepository<Customer> customerRepository)
{
_customerRepository = customerRepository;
} public int GetCustomerCount(ISpecification<Customer> spec)
{
return _customerRepository.Count(spec.ToExpression());
}
}

  这是非常简单的。我们可以传递任何规约到仓储,因为仓储可以使用表达式作为过滤器。在这个例子中,CustomerManager是不需要的,因为我们可以直接在仓储里使用规约查询数据库。但是,我们想在一些客户上执行业务操作,在这种情况下,我们可以在领域服务里使用规约指定需要操作的客户。

组合规约

  规约一个强大的特征是可以使用And,Or,Not和AndNot扩展方法进行组合使用。示例:

var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification().And(new CustomerRegistrationYearSpecification()));

  我们甚至可以基于已有的规约创建一个新的规约:

public class NewPremiumCustomersSpecification : AndSpecification<Customer>
{
public NewPremiumCustomersSpecification()
: base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification())
{
}
}

  AndSpecification是Specification类的一个子类,这个类只有两个规约都满足时才满足。因此我们可以像其他规约那样使用NewPremiumCustomersSpecification:

var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());

讨论

  因为规约模式比C#拉姆达表达式久远,它经常和表达式比较。一些开发者可能认为不再需要规约模式,我们可以直接传递表达式给仓储或领域服务,如下:

var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == );

  因为ABP仓储支持表达式,所以这种使用方式完全有效。你不需要在应用里定义或使用任何规约,你可以继续使用表达式。所以,规约的点是什么?为什么还有什么时候我们该考虑使用它呢?

什么时候使用?

  使用规约的一些好处:

  • 可复用:设想你在代码的很多地方都需要使用PremiumCustomer过滤器。如果你使用表达式而不是创建规约,如果以后会更改“Premium Customer”的定义(比如,你想要更改优质的标准从$100000到$250000并且添加另一个条件如客户必须大于3)将会放生什么呢。如果你使用规约,仅仅需要更改一个类。如果你使用(复制/粘贴)同样的表达式,需要全部更改他们。
  • 可组合:你可以组合多个规约创建一个新的规约。这是另一种类型的复用。
  • 命名的:PremiumCustomerSpecification比使用复杂的表达式更能清晰的表达意图。所以,如果在业务中这个表达式是有意义的,考虑使用规约。
  • 可测试:规约是独立易测试的对象。

什么时候不使用? 

  • 没有业务表达式:你可以考虑不使用规约,如果表达式、操作没有业务的话。
  • 报表:如果你仅仅创建一个报表,就不要创建规约,直接使用IQueryable。实际上,你甚至可以使用平常的SQL、师徒和其他报表工具。DDD对报表不怎么关心,从性能角度来讲,可以使用数据存储查询的好处是非常重要的。

返回主目录

ABP官方文档翻译 3.5 规约的更多相关文章

  1. ABP官方文档翻译 10.1 ABP Nuget包

    ABP Nuget包 Packages Abp Abp.AspNetCore Abp.Web.Common Abp.Web Abp.Web.Mvc Abp.Web.Api Abp.Web.Api.OD ...

  2. ABP官方文档翻译 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  3. 0.0 ABP官方文档翻译目录

    一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...

  4. ABP官方文档翻译 2.5 设置管理

    设置管理 介绍 关于 ISettingStore 定义设置 设置范围 重写设置定义 获取设置值 服务端 客户端 更改设置 关于缓存 介绍 每个应用都需要存储设置,并且在应用的某些地方需要使用这些设置. ...

  5. ABP官方文档翻译 9.3 NHibernate集成

    NHibernate集成 Nuget包 配置 实体映射 仓储 默认实现 自定义仓储 应用程序特定基础仓储类 ABP可以使用任何ORM框架,它内置集成NHibernate.此文档将讲解ABP如何使用NH ...

  6. ABP官方文档翻译 9.2 Entity Framework Core

    Entity Framework Core 介绍 DbContext 配置 在Startup类中 在模块PreInitialize方法中 仓储 默认仓储 自定义仓储 应用程序特定基础仓储类 自定义仓储 ...

  7. ABP官方文档翻译 9.1 EntityFramework集成

    EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内 ...

  8. ABP官方文档翻译 8.2 SignalR集成

    SignalR集成 介绍 安装 服务器端 客户端 建立连接 內建特征 通知 在线客户端 PascalCase与CamelCase对比 你的SignalR代码 介绍 ABP中的Abp.Web.Signa ...

  9. ABP官方文档翻译 8.1 通知系统

    通知系统 介绍 发送模型 通知类型 通知数据 通知严重性 关于通知持久化 订阅通知 发布通知 用户通知管理 实时通知 客户端 通知存储 通知定义 介绍 在系统中通知用来基于特定的事件告知用户.ABP提 ...

随机推荐

  1. UVALive3882-And Then There Was One-约瑟夫问题-递推

    And Then There Was One Time limit: 3.000 seconds Let's play a stone removing game. Initially, n ston ...

  2. c++(排序二叉树)

    前面我们讲过双向链表的数据结构.每一个循环节点有两个指针,一个指向前面一个节点,一个指向后继节点,这样所有的节点像一颗颗珍珠一样被一根线穿在了一起.然而今天我们讨论的数据结构却有一点不同,它有三个节点 ...

  3. PhpStorm中如何使用Xdebug工具,入门级操作方法

    http://blog.csdn.net/knight_quan/article/details/51953269 1.简介: PhpStorm是一个轻量级且便捷的PHP IDE,其提供的智能代码补全 ...

  4. nginx https配置后无法访问,可能防火墙在捣鬼

    同事发现nginx配置后https 无法访问,我帮忙解决的时候从以下出发点 1.防火墙未开放443端口 2.配置出错 1 2 3 于是就 netstat -anp 查看防火墙开的端口 发现已经在监听了 ...

  5. dede内容页调用图片集下所有图片方法!

    http://blog.csdn.net/forest_fire/article/details/50943765 版权声明:本文为博主原创文章,未经博主允许不得转载. {dede:productim ...

  6. PHP网站从Apache转移到Nginx后产生404错误的原因和解决办法

    原案例分析: 1.原来的网站在wamp环境下搭建完成,一切正常,上传到虚拟主机环境为lnmp,结果访问时可以打开主页,然后点其他页面全部报404错误: 2.经分析得出原因:原网站环境为wamp使用了伪 ...

  7. PHP网站常见安全漏洞,及相应防范措施总结

    目前,基于PHP的网站开发已经成为目前网站开发的主流,本文笔者重点从PHP网站攻击与安全防范方面进行探究,旨在减少网站漏洞,希望对大家有所帮助! 一.常见PHP网站安全漏洞 对于PHP的漏洞,目前常见 ...

  8. Servlet&&Jsp 概述

    主题 Servlet的作用 构建动态网页 Servlet代码初探 Servlet与其他技术的对比 Jsp的作用 Servlet的作用 Servlet是在web服务器或应用服务器上用来动态生成html的 ...

  9. Python random模块(获取随机数)

    1.random.random 随机生成一个0到1的随机浮点数: 0 <= n < 1.0 In [2]: print random.random() 0.544824016934 2.r ...

  10. Linux - ubuntu vMwareTools安装

    ubuntu vMwareTools安装 不安装很麻烦,虚拟机中的内容,包括文件.无法复制到pc端.同样的pc端的内容也无法复制到虚拟机中. 1.点击虚拟机,选择安装VMwareTools 这个时候就 ...