ABP框架系列之四十八:(Specifications-规范)
Introduction
Specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic (Wikipedia).
In pratical, it's mostly used to define reusable filters for entities or other business objects.
规范模式是一种特殊的软件设计模式,通过使用布尔逻辑将业务规则链接在一起,可以重新组合业务规则。
Example
In this section, we will see the need for specification pattern. This section is generic and not related to ABP's implementation.
在本节中,我们将看到规范模式的必要性。本节是通用的,与ABP的实现无关。
Assume that you have a service method that calculates total count of your customers as shown below:
public class CustomerManager
{
public int GetCustomerCount()
{
//TODO...
return 0;
}
}
You probably will want to get customer count by a filter. For example, you may have premium customers (which have balance more than $100,000) or you may want to filter customers just by registration year. Then you can create other methods like GetPremiumCustomerCount(), GetCustomerCountRegisteredInYear(int year), GetPremiumCustomerCountRegisteredInYear(int year) and more. As you have more criteria, it's not possible to create a combination for every possibility.
您可能希望通过过滤器获得客户计数。例如,您可能有高级客户(余额超过100000美元),或者您可能希望通过注册年来过滤客户。然后,您可以创建其他方法如getpremiumcustomercount(),getcustomercountregisteredinyear(int year),getpremiumcustomercountregisteredinyear(int year)和更多的。由于有更多的标准,所以不可能为每种可能性创建一个组合。
One solution to this problem is the specification pattern. We could create a single method that gets a parameter as the filter:
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 = 0; foreach (var customer in customers)
{
if (spec.IsSatisfiedBy(customer))
{
customerCount++;
}
} return customerCount;
}
}
Thus, we can get any object as parameter that implements ISpecification<Customer> interface which is defined as shown below:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T obj);
}
And we can call IsSatisfiedBy with a customer to test if this customer is intented. Thus, we can use same GetCustomerCount with different filters without changing the method itself.
我们可以调用issatisfiedby用客户去测试这个客户是否希望。因此,我们可以使用相同的getcustomercount,传递不同滤波器,方法本身不改变。
While this solution is pretty fine in theory, it should be improved to better work in C#. For instance, it's not efficient to get all customers from database to check if they satisfy the given specification/condition. In the next section, we will see ABP's implementation which overcome this problem.
虽然这个方案在理论上是很好的,应该改进的更好的工作在C #。例如,从数据库中获取所有客户来检查它们是否满足给定的规格/条件是没有效率的。在下一节中,我们将看到ABP的实现来克服这个问题。
Creating Specification Classes
ABP defines the ISpecification interface as shown below:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T obj); Expression<Func<T, bool>> ToExpression();
}
Adds a ToExpression() method which returns an expression and used to better integrate with IQueryable and Expression trees. Thus, we can easily pass a specification to a repository to apply a filter in the database level.
增加了一个toexpression()方法返回一个表达式,用于更好地整合IQueryable和表达式树。因此,我们可以轻松地将规范传递到存储库,以便在数据库级别应用筛选器。
We generally inherit from Specification<T> class instead of directly implementing ISpecification<T> interface. Specification class automatically implements IsSatisfiedBy method. So, we only need to define ToExpression. Let's create some specification classes:
我们一般从规范<T>类而不是直接实施ispecification <T>接口。规范类自动实现issatisfiedby方法。所以,我们只需要定义到。让我们创建一些规范类:
//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 >= 100000);
}
} //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);
}
}
As you see, we just implemented simple lambda expressions to define specifications. Let's use these specifications to get count of customers:
如您所见,我们只是实现了简单的lambda表达式来定义规范。让我们用这些规格来计算顾客的数量。
count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification(2017));
Using Specification With Repository(使用仓储规范)
Now, we can optimize CustomerManager to apply filter in the database:
现在,我们可以优化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());
}
}
It's that simple. We can pass any specification to repositories since repositories can work with expressions as filters. In this example, CustomerManager is unnecessary since we could directly use repository with the specification to query database. But think that we want to execute a business operation on some customers. In that case, we could use specifications with a domain service to specify customers to work on.
就是这么简单。我们可以将任何规范传递给存储库,因为存储库可以使用表达式作为过滤器。在这个例子中,客户经理是不必要的因为我们可以直接使用库的规范来查询数据库。但是,我们想在某些客户上执行业务操作。在这种情况下,我们可以使用带有域服务的规范来指定要工作的客户。
Composing Specifications(排版规范)
One powerful feature of specifications is that they are composable with And, Or, Not and AndNot extension methods. Example:
规格的一个强大的特点是,他们是组合的,或者,不,而不是扩展方法。例子:
var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification().And(new CustomerRegistrationYearSpecification(2017)));
We can even create a new specification class from existing specifications:
public class NewPremiumCustomersSpecification : AndSpecification<Customer>
{
public NewPremiumCustomersSpecification()
: base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification(2017))
{
}
}
AndSpecification is a subclass of Specification class which satisfies only if both specifications are satisfied. Then we can use NewPremiumCustomersSpecification just like any other specification:
规范是规范类满足只有当满足一类规范。然后我们可以使用newpremiumcustomersspecification就像任何其他规范:
var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());
Discussion
While specification pattern is older than C# lambda expressions, it's generally compared to expressions. Some developers may think it's not needed anymore and we can directly pass expressions to a repository or to a domain service as shown below:
而规范的模式比C # lambda表达式古老,它通常与表达相比。一些开发人员可能认为不再需要它,我们可以直接将表达式传递到存储库或域服务,如下所示:
var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == 2017);
Since ABP's Repository supports expessions, this is completely a valid usage. You don't have to define or use any specification in your application and you can go with expressions. So, what's the point of specification? Why and when should we consider to use them?
由于ABP的库支持的表达,这完全是一个有效的使用。您不必在应用程序中定义或使用任何规范,也可以使用表达式。因此,规范的重点是什么?为什么和什么时候我们应该考虑使用它们?
When To Use?
Some benefits of using specifications:
- Reusabe: Think that you need to PremiumCustomer filter in many places in your code base. If you go with expressions and not create a specification, what happens if you later change "Premium Customer" definition (say, you want to change minimum balance from $100,000 to $250,000 and add another condition like to be a customer older than 3). If you used specification, you just change a single class. If you used (copy/paste) same expression, you need to change all of them.
- Reusabe:认为你需要premiumcustomer过滤器在您的代码库,很多地方。如果您使用表达式而不是创建规范,如果您稍后更改“高级客户”定义(例如,您希望将最小余额从100000美元更改为250000美元,并添加另一个条件,如3岁以上的客户),会发生什么?。如果使用了规范,只需更改一个类。如果您使用(复制/粘贴)相同的表达式,则需要更改所有表达式。
- Composable: You can combine multiple specification to create new specifications. This is another type of reusability.
- 组合:可以将多个规范来创建新的规格。这是另一种类型的可重用性。
- Named: PremiumCustomerSpecification better explains the intent rather than a complex expression. So, if you have an expression that is meaningful in your business, consider to use specifications.
- 命名:premiumcustomerspecification更好的解释的意图,而不是一个复杂的表达式。因此,如果在业务中有一个有意义的表达式,请考虑使用规范。
- Testable: A specification is separately (and easily) testable object.
- 可测试性:规范是分开的(易于)可测试对象。
When To Not Use?
- Non business expressions: You can consider to not use specifications for non business related expressions and operations.
- 非业务表达式:您可以考虑不使用与非业务相关的表达式和操作的规范。
- Reporting: If you are just creating a report do not create specifications, but directly use IQueryable. Actually, you can even use plain SQL, Views or another tool for reporting. DDD does not care on reporting much and getting querying benefits of underlying data store can be important from performance point of view.
- 报告:如果你只是创建一个报告不创造规范,而是直接使用IQueryable。实际上,您甚至可以使用简单的SQL、视图或其他工具进行报告。DDD不关心报告太多,而从性能角度来看底层数据存储的查询好处是很重要的。
ABP框架系列之四十八:(Specifications-规范)的更多相关文章
- ABP框架系列之四十九:(Startup-Configuration-启动配置)
ASP.NET Boilerplate provides an infrastructure and a model to configure it and modules on startup. A ...
- ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)
Data Transfer Objects are used to transfer data between Application Layer and Presentation Layer. 数据 ...
- ABP框架系列之三十八:(NHibernate-Integration-NHibernate-集成)
ASP.NET Boilerplate can work with any O/RM framework. It has built-in integration with NHibernate. T ...
- ABP框架系列之四十四:(OWIN)
If you are using both of ASP.NET MVC and ASP.NET Web API in your application, you need to add Abp.Ow ...
- ABP框架系列之四十六:(Setting-Management-设置管理)
Introduction Every application need to store some settings and use these settings in somewhere in th ...
- ABP框架系列之四十二:(Object-To-Object-Mapping-对象映射)
Introduction It's a common to map a similar object to another object. It's also tedious and repeatin ...
- ABP框架系列之四十五:(Quartz-Integration-Quartz-集成)
Introduction Quartz is a is a full-featured, open source job scheduling system that can be used from ...
- ABP框架系列之四十:(Notification-System-通知系统)
Introduction Notifications are used to inform users on specific events in the system. ASP.NET Boiler ...
- ABP框架系列之三十四:(Multi-Tenancy-多租户)
What Is Multi Tenancy? "Software Multitenancy refers to a software architecture in which a sing ...
随机推荐
- 安装ORACLE高可用RAC集群11g执行root脚本的输出信息
安装ORACLE高可用RAC集群11g执行root脚本的输出信息 作者:Eric 微信:loveoracle11g [root@node1 ~]# /u01/app/oraInventory/orai ...
- struts设置开发者模式
struts设置开发者模式 在使用ssh框架做项目时,struts.xml文件总要配置许多项功能,其中一个就是开发者模式: <constant name="struts.devMode ...
- k8s学习笔记之六:Pod控制器(kube-controller-manager)
第一章.什么是kube-controller-manager? Controller Manager 由 kube-controller-manager 和 cloud-controller-mana ...
- Oracle导出表数据与导入表数据dmp,以及导入导出时候常见错误
使用DOS 操作界面导出表数据,导入表数据(需要在数据库所在的服务器上边执行) exp UserName/Password@192.168.0.141/orcl file=d:\xtables.d ...
- linux安装jdk8
1.文件准备 jdk-8u201-linux-x64.tar.gz 下载地址 http://www.oracle.com/technetwork/java/javase/downloads/jdk8- ...
- cut命令详解
1.简介:cut:以某种方式按照文件的行进行分割 2.参数列表: -b:仅显示行中指定直接范围的内容: -c:仅显示行中指定范围的字符: -d:指定字段的分隔符,默认的字段分隔符为“TAB”: -f: ...
- 安装好ubuntu双系统启动时卡死解决办法
问题描述:在安装完ubuntu双系统后,第一次启动ubuntu系统时,卡死在启动界面(或者黑屏),这大概都是由于显卡驱动的原因,具体不在这里阐述,通过以下方法能成功解决,据我个人经验,这可能是诸多方法 ...
- jQuery 新添加元素事件绑定无效
jQuery中事件绑定,大多使用on就足够了. 但是对于新添加的元素 on 的绑定事件 会不起作用. 因为 append 中的 节点是在整个文档加载之后才添加的,页面并不会为未来的元素初始化添加点击事 ...
- 记账本,C,Github,Dao
package dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSe ...
- mongodb文件损坏的恢复--无可恢复数据
1.mongodb 启动异常error code 100,检查日志,数据文件损坏 2 检查collection-15-6548623434943640018.wt 可恢复数据,为空,不存在恢复的数据 ...