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-规范)的更多相关文章

  1. ABP框架系列之四十九:(Startup-Configuration-启动配置)

    ASP.NET Boilerplate provides an infrastructure and a model to configure it and modules on startup. A ...

  2. ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)

    Data Transfer Objects are used to transfer data between Application Layer and Presentation Layer. 数据 ...

  3. ABP框架系列之三十八:(NHibernate-Integration-NHibernate-集成)

    ASP.NET Boilerplate can work with any O/RM framework. It has built-in integration with NHibernate. T ...

  4. 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 ...

  5. ABP框架系列之四十六:(Setting-Management-设置管理)

    Introduction Every application need to store some settings and use these settings in somewhere in th ...

  6. ABP框架系列之四十二:(Object-To-Object-Mapping-对象映射)

    Introduction It's a common to map a similar object to another object. It's also tedious and repeatin ...

  7. ABP框架系列之四十五:(Quartz-Integration-Quartz-集成)

    Introduction Quartz is a is a full-featured, open source job scheduling system that can be used from ...

  8. ABP框架系列之四十:(Notification-System-通知系统)

    Introduction Notifications are used to inform users on specific events in the system. ASP.NET Boiler ...

  9. ABP框架系列之三十四:(Multi-Tenancy-多租户)

    What Is Multi Tenancy? "Software Multitenancy refers to a software architecture in which a sing ...

随机推荐

  1. kubernetes核心组件kube-proxy 学习总结

    一.  kube-proxy 和 service  kube-proxy是Kubernetes的核心组件,部署在每个Node节点上,它是实现Kubernetes Service的通信与负载均衡机制的重 ...

  2. 搭建Tomcat应用服务器、tomcat虚拟主机及Tomcat多实例部署

    一.环境准备 系统版本:CentOS release 6.6 (Final) x86_64 Tomcat版本:tomcat- JDK版本:jdk-8u25-linux-x64 关闭防火墙 软件包下载地 ...

  3. 八(第二篇)、主体结构元素——nav元素、aside元素

    nav元素 nav元素是一个可以用作页面导航的链接组,其中的导航元素链接到其他页面或当前页面的其他部分. 并不是所有的链接组都要被放进nav元素,只需要将主要的.基本的链接组放进nav元素即可. na ...

  4. Oracle 学习笔记(二)

    一.索引 表的数据是无序的,所以叫堆表(heap table),意思为随机存储数据.因为数据是随机存储的,所以在查询的时候需要全表扫描.索引就是将无序的数据有序化,这样就可以在查询数据的时候 减少数据 ...

  5. Requests对HTTPS请求验证SSL证书

    SSL证书通过在客户端浏览器和Web服务器之间建立一条SSL安全通道(Secure socket layer(SSL)安全协议是由Netscape Communication公司设计开发.该安全协议主 ...

  6. android 开发 View _5_ Paint详解

    转载:http://blog.csdn.net/abcdef314159 //Paint的setStyle,Style共有3种 setStyle(Style style) Paint.Style.FI ...

  7. 《算法导论》——顺序统计RandomizedSelect

    RandomizedSelect.h: #include <stdlib.h> namespace dksl { /* *交换 */ void Swap(int* numArray,int ...

  8. ListView的基本使用方法和RecyclerView的基本使用方法

    ListView是一种用于列表显示数据内容的控件,它可以通过适配器实现对于数据的列表显示,而RecyclerView是对于ListView优化后的列表数据显示控件. 个人对于List的使用经历多半在新 ...

  9. Android关于API level、buildToolVersion、CompileSdkVersion

    API level: API level是一个整数,它指的是我们使用的框架(Framework)的版本,也就是我们使用的sdk中的各个平台下的android.jar. 但是这个API level又和A ...

  10. cdnbest 节点和主控连接不上原因主要查看几点

    1. 注意安装过程中有没有报错,如果没有报错,检查下节点程序是否有运行,本例以linux系统为例,windows系统可以查看进程管理器 有以下进程说明程序是运行成功的 ps -aux |grep ka ...