规约

介绍

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

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

示例

  在这个部分,我们将看到规约模式的必要性。本部分是通用的,和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. 【C#学习笔记之一】C#中的关键字

    C#中的关键字 关键字是对编译器具有特殊意义的预定义保留标识符.它们不能在程序中用作标识符,除非它们有一个 @ 前缀.例如,@if 是有效的标识符,但 if 不是,因为 if 是关键字. 下面是列出的 ...

  2. Codeforces 839A Arya and Bran【暴力】

    A. Arya and Bran time limit per test:1 second memory limit per test:256 megabytes input:standard inp ...

  3. [bzoj1301] [LLH邀请赛]参观路线

    本题同bzoj1098 用个并查集,把连续的被访问过的点并起来..这样就不会尝试已经走过的点了. #include<cstdio> #include<iostream> #in ...

  4. 迷宫问题(bfs+记录路径)

    题目链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=105278#problem/K K - 迷宫问题 Time Limit:1000 ...

  5. Myeclipse xml标签代码提示,引入schema

    以SpringMVC为例 先引入命名空间 需要配置 xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schema ...

  6. 【Sql】mySQL在windows环境启动

    SQL的不同版本在Windows环境启动配置方法不同,此处仅介绍 5.7.20的配置方法: 1.登录mysql官网下载windows环境下的工具压缩包 http://dev.mysql.com/dow ...

  7. echarts图表里label文字过长换行的方法

    在做一些图标时,有时会出现显示文字过长的问题,需要将其按照指定的字数换行,像下图这样 而echarts没有提供换行的方法,但是可以使用fomatter方法进行设置,代码如下 formatter: fu ...

  8. C++输出二进制文件和文本文件

    所谓二进制文件和文本文件对于字母而言没有什么不同,都是存储该字母的ASCII码值.能引起不同的是数字和一些排版用符号的格式. 数字在二进制文件中会存储该数字的值,而文本文件中则首先将该数字视为字符量, ...

  9. POJ 2387 Til the Cows Come Home(模板——Dijkstra算法)

    题目连接: http://poj.org/problem?id=2387 Description Bessie is out in the field and wants to get back to ...

  10. phpMyAdmin访问远程MySQL数据库的方法

    本地phpmyadmin远程连接服务器端MySQL 首先要确定你的mysql远程连接已开启,如果没有开启按照下面的二个方法操作: 方法一:改表法 因为在linux环境下,默认是关闭3306端口远程连接 ...