ABP框架 - 規約

文檔目錄html

 

本節內容:數據庫

 

簡介express

規約模式是一個特別的軟件設計模式,業務邏輯可使用boolean邏輯從新連接業務邏輯(維基百科).設計模式

實踐中的大部分狀況,它是爲實體或其它業務對象,定義可複用的過濾器.ide

 

示例工具

在此小節,咱們將看到規約模式的必要性,這節是通用的,與ABP的實現無關.性能

假設你有一個服務方法用來計算客戶的總數,如:測試

public class CustomerManager
{
    public int GetCustomerCount()
    {
        //TODO...
        return 0;
    }
}

 你可能想經過一個過濾器獲取客戶數,例如:你有優質的客戶(餘額超過$100,000)或根據註冊年份過濾客戶,因而你能夠建立其它方法,如:GetPremiumCustomerCount(),GetCustomerCountRegisteredInYear(int year),GetPremiumCustomerCountRegisterdInYear(int year)等等,當你有更多條件時,不可能爲每種狀況建立一個組合.優化

這個問題的一個解決方法是規約模式,咱們能夠建立一個單獨的方法,獲取一個參數做爲過濾器:spa

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);
}

而後咱們能夠爲一個customer調用 IsSatisfiedBy 來檢查這個customer是否符合意圖.所以,咱們可用不一樣的過濾器調用相同的GetCustomerCount方法,不須要修改方法自己.

理論上這個方案很好了,但在C#中能夠改進得更完美, 例如:從數據庫中獲取全部的客戶來檢查是否知足給定的規約/條件,這可不是頗有效率,接下來的小節裏,咱們將看到ABP的實現,它解決了這個問題.

 

建立規約類

ABP定義了以下的ISpecification接口:

public interface ISpecification<T>
{
    bool IsSatisfiedBy(T obj);

    Expression<Func<T, bool>> ToExpression();
} 

添加一個  ToExpression() 方法,它返回一個表達式(expression),用來更好地結合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);
    }
}

 如你所見,咱們只是實現簡單的lambda表達式來定義規約,讓咱們用這些規約獲取客戶數:

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不是必要的,所以咱們能夠直接使用倉儲和規約來查詢數據庫,但考慮一下:咱們想在某客戶上執行一個業務操做,咱們可使用規約和一個領域服務來指定customers,從而繼續工做.

 

組合規約

有個強大的功能:能夠用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 是一個子類,它僅在兩個規約都知足的狀況下才符合條件,而後咱們可使用NewPremiumCustomersSpecification ,就像其它的規約同樣:

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

 

討論

因爲規約模式比C#的lambda表達式舊,因此經過拿來與表達式對比.一些開發者可能認爲規約模式再也不須要,咱們能夠直接傳遞表達式給一個倉儲或領域服務,如:

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

因爲ABP的倉儲支持表達式,這是一個合法的用法,在應用裏,你不是必定要定義或使用任何規約,因而你能夠繼承使用表達式,因此,規約的要點在哪?爲什麼,什麼時候應當考慮使用它們?

 

什麼時候使用

使用規約的好處:

  • 可複用:設想你須要在你代碼裏多處用到"優質客戶"過濾,若是你使用表達式而不建立一個規約,那若是在之後你須要修改"優質客戶"的定義會發生什麼(如:你想改爲餘額至少從$100,000到250,000幷包含其它條件,諸如客戶註冊超過3年),若是你使用規約,你只須要修改一個類,若是你使用(複製/粘貼)相同的表達式,你就要修改全部用到的地方.
  • 可組合:你能夠聯合多個規約來建立新規約,這是另外一種形式的複用.
  • 命名化:PremiumCustomerSpecification比一個複雜的表達式更好地表述了意圖, 因此,若是你想一個表達式在你的業務裏變得可顧名思義,那麼考慮使用規約.
  • 可測試:一個規約是一個單獨的(且易於)可測試的對象.

 

什麼時候不用

  • 沒有業務表達式: 你能夠在你的業務不涉及表達式和操做時不使用規約.
  • 建立報表:若是你只是建立一個報表就不要用規約,而直接使用IQueryable,實際上,你甚至可使用原始的SQL,視圖或其它報表工具.DDD不關心報表和底層數據庫存儲獲取查詢好處及視圖性能.

 

英文原文:http://www.aspnetboilerplate.com/Pages/Documents/Specifications

相關文章
相關標籤/搜索