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對報表不怎麽關心,從性能角度來講,可以使用數據存儲查詢的好處是非常重要的。
返回主目錄
ABP官方文檔翻譯 3.5 規約