step_by_step_ABP规约模式

  一段时间没有在github 上浏览ABP项目,几天前看到ABP新增规约模式,开始了解并学习文档   记录一下

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.
你可能希望通过过滤器获得客户数,例如 你也许有高级客户(余额超过100000美元) 或者你希望通过注册年限过滤客户.然后你会创建很多像 GetPremiumCustomerCount(),GetCustomerCountRegisteredInYear(int year) ,GetPremiumCustomerCountRegisteredInYear(int year)这样的方法,

As you have more criteria, it's not possible to create a combination for every possibility
由于你有更多的条件,因此无法为每一种可能性创建组合.
One solution to this problem is the specification pattern. We could create a single method that gets a parameter as the filter:
这个问题的解决方案便是规约模式,我们可能创建一个单独的方法为过滤器获取参数:
  

 1 public class CustomerManager
 2 {
 3   private readonly IRepository<Customer> _customerRepository;
 4 
 5   public CustomerManager(IRepository<Customer> customerRepository)
 6   {
 7     _customerRepository = customerRepository;
 8   }
 9 
10     public int GetCustomerCount(ISpecification<Customer> spec)
11     {
12         var customers = _customerRepository.GetAllList();
13 
14         var customerCount = 0;
15     
16         foreach (var customer in customers)
17         {
18             if (spec.IsSatisfiedBy(customer))
19             {
20                 customerCount++;
21             }
22         }
23 
24         return customerCount;
25     }
26 }            

Thus, we can get any object as parameter that implements ISpecification<Customer> interface which is defined as shown below:
因此,我们可以得到一个实现了ISpecification<Customer> 接口的参数 ,其定义如下所示:

1 public interface ISpecification<T>
2 {
3     bool IsSatisfiedBy(T obj);
4 }


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:
ABP 定义接口 ISpecification 如下所示:

1 public interface ISpecification<T>
2 {
3     bool IsSatisfiedBy(T obj);
4 
5     Expression<Func<T, bool>> ToExpression();
6 }

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:
我们通常继承 Specification<T> 类而不是直接实现 ISpecification<T> 接口 .Specification 类 自动的实现 IsSatisfiedBy方法 .因此 我们仅仅需要去定义ToExpression . 让我们创建 一些 specification 类 :

  

 1 //Customers with $100,000+ balance are assumed as PREMIUM customers.
 2 public class PremiumCustomerSpecification : Specification<Customer>
 3 {
 4     public override Expression<Func<Customer, bool>>  ToExpression()
 5   {
 6     return (customer) => (customer.Balance >= 100000);
 7   }
 8 }
 9 
10 //A parametric specification example.
11 public class CustomerRegistrationYearSpecification : Specification<Customer>
12 {
13   public int Year { get; }
14 
15   public CustomerRegistrationYearSpecification(int year)
16   {
17     Year = year;
18   }
19 
20   public override Expression<Func<Customer, bool>> ToExpression()
21   {
22     return (customer) => (customer.CreationYear == Year);
23   }
24 }

As you see, we just implemented simple lambda expressions to define specifications. Let's use these specifications to get count of customers:
正如你看到的,我们仅仅实现一个lambda 表达式去定义 规则.让我们使用这些规则去得到客户数:

var count = customerManager.GetCustomerCount(new PremiumCustomerSpecification());
var count = customerManager.GetCustomerCount(new CustomerRegistrationYearSpecification(2017));

Using Specification With Repository
在仓储中使用规则
Now, we can optimize CustomerManager to apply filter in the database:
现在 ,我们可以优化 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.
就是这么简单 .我们可以将任何规则传递给仓储,因为仓储可以将表达式作为过滤器.在这个例子中 ,CustomerManager 是不必要的,因为我们可以直接使用仓储通过 规则去查询数据库 ,但是我们想要在一些客户上执行一个业务逻辑操作.在这种情况下,我们可以使用具有领域服务的规范来指定要处理的客户。
Composing Specifications
组合规则
One powerful feature of specifications is that they are composable with And, Or, Not and AndNot extension methods. Example:
规约一个强大的功能是,它们可以组合使用AND,OR和 ANDNOT 扩展方法 例:

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:
AndSpecification是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.
可重用的:在你的代码库的许多地方都要使用到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不关心报告太多,从性能的角度来看,获取底层数据存储的查询优势可能很重要。

原文地址:https://www.cnblogs.com/lucky528/p/6581261.html