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. 知识点:SQL中char、varchar、text区别

    Char为定长,varchar,text为变长. 1.CHAR.CHAR存储定长数据很方便,CHAR字段上的索引效率级高,比如定义char(10),那么不论你存储的数据是否达到了10个字节,都要占去1 ...

  2. c# HashTable (哈希表)

    HashTable 哈希表 也是System.Collections集合下的数据结构类 它储存的也是Object类型的对象 但是它在内存中是散列排布的 因为这个特性,非常适合存储大量的数据 在Hash ...

  3. 使用css实现时间轴

    本文将使用css来实现一个左右交叉布局的时间轴,效果如下: 使用的都是一些常用的css,代码如下: <!DOCTYPE> <html> <head> <tit ...

  4. QQ第三方登录(预备节)

    第一次很多时候,用户因为复杂的注册程序,而不愿意再体验我们的网站服务,我们可以通过QQ第三方授权,这样既保证了用户数,而且也极大的提高了用户体验.废话就不多说了,直接进入正题... 要实现QQ第三方登 ...

  5. Python 内置os模块的简单实用

    获取路径&目录添加文件 在自动化测试的过程,考虑到工程文件的移动或者在其他人的工作环境中运行,所以我们的路径要灵活,不能把路径写死. 推荐使用Python的内置模块OS 参照图 import ...

  6. Oracle SQL 优化规则

    一.问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用 系统提交实际应用后,随着数据库中数据的增加,系 ...

  7. 学习excel的使用技巧二批量复制

    1 选中要操作的部分 2 CTRL+G 打开定位 3 点击 定位条件 4 选择空值 5 输入=号  然后键盘的 方向键  向上 6 按住CTRL+回车 即可实现  批量复制

  8. mysql数据库支持 emoji表情

    关键点: 1. 修改mysql数据库表ALTER table <tablename>  modify <clolum>  char(100) character set utf ...

  9. Array.prototype.push.apply(a,b)和Array.prototype.slice.call(arguments)

    Array.prototype.push.apply(a,b) 时常看到在操作数组的时候有这样的写法: var a = [1,2,3]; var b = [4,5,6]; a.push.apply(a ...

  10. Spring学习,初识Spring

    Spring概述 将Spring理解为管理对象间依赖关系的容器 “解耦” 根据功能的不同,可以将一个系统中的代码分为 主业务逻辑 与 系统级业务逻辑 两类 ```Spring 是为了解决企业级开发的复 ...