ABP官方文档翻译 3.5 规约

规约

介绍

  规约模式是一种特别的软件设计模式,通过使用布尔逻辑将业务规则链接起来重新调配业务规则。(维基百科)。

  尤其是,它通常用来为实体或其他业务对象定义可复用的过滤器。

示例

  在这个部分,我们将看到规约模式的必要性。本部分是通用的,和ABP的实现没有必然的关系。

  假定,有一个服务方法,计算所有客户的总数量,如下所示:

复制代码
public class CustomerManager
{
    public int GetCustomerCount()
    {
        //TODO...
        return 0;
    }
}
复制代码

  你或许希望通过过滤器获取客户数量。例如,你有优质客户(拥有超过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 = 0;
        
        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 >= 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);
    }
}
复制代码

  如你所见,我们仅仅实现了简单的拉姆达表达式来定义规约。让我们使用这些规约获取客户的数量:

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

使用仓储规约

  现在,我们优化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(2017)));

  我们甚至可以基于已有的规约创建一个新的规约:

复制代码
public class NewPremiumCustomersSpecification : AndSpecification<Customer>
{
    public NewPremiumCustomersSpecification() 
        : base(new PremiumCustomerSpecification(), new CustomerRegistrationYearSpecification(2017))
    {
    }
}
复制代码

  AndSpecification是Specification类的一个子类,这个类只有两个规约都满足时才满足。因此我们可以像其他规约那样使用NewPremiumCustomersSpecification:

var count = customerManager.GetCustomerCount(new NewPremiumCustomersSpecification());

讨论

  因为规约模式比C#拉姆达表达式久远,它经常和表达式比较。一些开发者可能认为不再需要规约模式,我们可以直接传递表达式给仓储或领域服务,如下:

var count = _customerRepository.Count(c => c.Balance > 100000 && c.CreationYear == 2017);

  因为ABP仓储支持表达式,所以这种使用方式完全有效。你不需要在应用里定义或使用任何规约,你可以继续使用表达式。所以,规约的点是什么?为什么还有什么时候我们该考虑使用它呢?

什么时候使用?

  使用规约的一些好处:

  • 可复用:设想你在代码的很多地方都需要使用PremiumCustomer过滤器。如果你使用表达式而不是创建规约,如果以后会更改“Premium Customer”的定义(比如,你想要更改优质的标准从$100000到$250000并且添加另一个条件如客户必须大于3)将会放生什么呢。如果你使用规约,仅仅需要更改一个类。如果你使用(复制/粘贴)同样的表达式,需要全部更改他们。
  • 可组合:你可以组合多个规约创建一个新的规约。这是另一种类型的复用。
  • 命名的:PremiumCustomerSpecification比使用复杂的表达式更能清晰的表达意图。所以,如果在业务中这个表达式是有意义的,考虑使用规约。
  • 可测试:规约是独立易测试的对象。

什么时候不使用? 

  • 没有业务表达式:你可以考虑不使用规约,如果表达式、操作没有业务的话。
  • 报表:如果你仅仅创建一个报表,就不要创建规约,直接使用IQueryable。实际上,你甚至可以使用平常的SQL、师徒和其他报表工具。DDD对报表不怎么关心,从性能角度来讲,可以使用数据存储查询的好处是非常重要的。

返回主目录

posted @ 2017-05-18 21:25  罗志均  阅读(159)  评论(0编辑  收藏  举报
友情链接:回力 | 中老年女装 | 武汉英语培训机构 | 留学费用一览表 | 托福考试费用