ABP官方文档翻译 3.5 规约
规约
介绍
规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则。(维基百科)。
尤其是,它通常用来为实体或其他业务对象定义可复用的过滤器。
示例
在这个部分,我们将看到规约模式的必要性。本部分是通用的,和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 规约的更多相关文章
- 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 ...
- ABP官方文档翻译 0.0 ABP官方文档翻译目录
一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...
- 0.0 ABP官方文档翻译目录
一直想学习ABP,但囿于工作比较忙,没有合适的契机,当然最重要的还是自己懒.不知不觉从毕业到参加工作七年了,没留下点儿什么,总感觉很遗憾,所以今天终于卯足劲鼓起勇气开始写博客.有些事能做的很好,但要跟 ...
- ABP官方文档翻译 2.5 设置管理
设置管理 介绍 关于 ISettingStore 定义设置 设置范围 重写设置定义 获取设置值 服务端 客户端 更改设置 关于缓存 介绍 每个应用都需要存储设置,并且在应用的某些地方需要使用这些设置. ...
- ABP官方文档翻译 9.3 NHibernate集成
NHibernate集成 Nuget包 配置 实体映射 仓储 默认实现 自定义仓储 应用程序特定基础仓储类 ABP可以使用任何ORM框架,它内置集成NHibernate.此文档将讲解ABP如何使用NH ...
- ABP官方文档翻译 9.2 Entity Framework Core
Entity Framework Core 介绍 DbContext 配置 在Startup类中 在模块PreInitialize方法中 仓储 默认仓储 自定义仓储 应用程序特定基础仓储类 自定义仓储 ...
- ABP官方文档翻译 9.1 EntityFramework集成
EntityFramework集成 Nuget包 DbContext 仓储 默认仓储 自定义仓储 应用特定的基础仓储类 自定义仓储示例 仓储最佳实践 事务管理 数据存储 ABP可以使用ORM框架,它内 ...
- ABP官方文档翻译 8.2 SignalR集成
SignalR集成 介绍 安装 服务器端 客户端 建立连接 內建特征 通知 在线客户端 PascalCase与CamelCase对比 你的SignalR代码 介绍 ABP中的Abp.Web.Signa ...
- ABP官方文档翻译 8.1 通知系统
通知系统 介绍 发送模型 通知类型 通知数据 通知严重性 关于通知持久化 订阅通知 发布通知 用户通知管理 实时通知 客户端 通知存储 通知定义 介绍 在系统中通知用来基于特定的事件告知用户.ABP提 ...
随机推荐
- 我的第五个网页制作:pre、html转义、abbr标签的使用
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- 浏览器的统一指针事件:Pointer Event
在早期的浏览器,输入的事件其实相对单纯,只有考虑到鼠标和键盘两种:而当时的鼠标事件,其实就是 click.mousedown.mouseup 等等的事件.但是当手机.平板开始流行时候,再移动装置上的主 ...
- 在form里面,放了四个UEditor,怎么在后台分别获取它们值
1) 默认情况下提交到后台的表单名称是 "editorValue",在editor_config.js中可以配置,参数名为textarea. 2) 可以在容器标签(即script标 ...
- ssh密码
- 面试官最爱的volatile关键字
在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性 ...
- PostgreSql问题:ERROR: operator does not exist: timestamp without time zone > character varying
问题描述: ERROR: operator does not exist: timestamp without time zone > character varying 解决方法: //注意 ...
- volatile特性及内存语义
1.volatile变量自身具有下列特性:·可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入.·原子性:对任意单个volatile变量的读/写具有原子 ...
- 使用Botkit和Rasa NLU构建智能聊天机器人
欢迎大家前往云+社区,获取更多腾讯海量技术实践干货哦~ 我们每天都会听到关于有能力涉及旅游.社交.法律.支持.销售等领域的新型机器人推出的新闻.根据我最后一次查阅的数据,单单Facebook Me ...
- css FlexBox 弹性盒子常用方法总结
总结一下弹性盒子常用的方法,弹性盒子的功能强大,这次我做了兼容性的felxbox,虽然代码多了一点,但在项目时候可以直接复制过来用,同时在项目上线的时候,如果这时候弹性盒子出了兼容问题,那就可急了~ ...
- Nginx日志中的金矿 -- 好文收藏
转:http://www.infoq.com/cn/articles/nignx-log-goldmine Nginx(读作Engine-X)是现在最流行的负载均衡和反向代理服务器之一.如果你是一名中 ...