趣味编程:C#中Specification模式的实现(参考答案 - 下)
上一篇文章中我們利用C#語(yǔ)言的特性實(shí)現(xiàn)了一種輕量級(jí)的Specification模式,它的關(guān)鍵在于拋棄了具體的Specification類型,而是使用一個(gè)委托對(duì)象代替唯一關(guān)鍵的IsSatisfiedBy方法邏輯。據(jù)我們分析,其優(yōu)勢(shì)之一在于使用簡(jiǎn)單,其劣勢(shì)之一在于無法靜態(tài)表示。但是它們還都是在處理“業(yè)務(wù)邏輯”,如果涉及到一個(gè)用于LINQ查詢或其他地方的表達(dá)式樹,則問題就不那么簡(jiǎn)單了——但也沒有我們想象的那么復(fù)雜。
好,那么我們就把場(chǎng)景假想至LINQ上。LINQ與普通業(yè)務(wù)邏輯不同的地方在于,它不是用一個(gè)IsSatisfiedBy方法或一個(gè)委托對(duì)象用來表示判斷邏輯,而是需要構(gòu)造一個(gè)表達(dá)式樹,一種數(shù)據(jù)結(jié)構(gòu)。如果您還不清楚表達(dá)式樹是什么,那么可以看一下腦袋的寫的上手指南。這是.NET 3.5帶來的重要概念,在4.0中又得到了重要發(fā)展,如果您要在.NET方面前進(jìn),這是一條必經(jīng)之路。
And、Or和Not之間,最容易處理的便是Not方法,于是我們從這個(gè)地方下手,直接來看它的實(shí)現(xiàn):
public static class SpecExprExtensions {public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> one){var candidateExpr = one.Parameters[0];var body = Expression.Not(one.Body);return Expression.Lambda<Func<T, bool>>(body, candidateExpr);} }一個(gè)Expression<TDelegate>對(duì)象中主要有兩部分內(nèi)容,一是參數(shù),二是表達(dá)式體(Body)。對(duì)于Not方法來說,我們只要獲取它的參數(shù)表達(dá)式,再將它的Body外包一個(gè)Not表達(dá)式,便可以此構(gòu)造一個(gè)新的表達(dá)式了。這部分邏輯非常簡(jiǎn)單,看了腦袋的文章,了解了表達(dá)式樹的基本結(jié)構(gòu)就能理解這里的含義。那么試驗(yàn)一下:
Expression<Func<int, bool>> f = i => i % 2 == 0; f = f.Not();foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f)) {Console.WriteLine(i); }打印出來的自然是所有的奇數(shù),即1、3、5。
而And和Or的處理上會(huì)有所麻煩,我們不能這樣像Not一樣簡(jiǎn)單處理:
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another) {var candidateExpr = one.Parameters[0];var body = Expression.And(one.Body, another.Body);return Expression.Lambda<Func<T, bool>>(body, candidateExpr); }這么做雖然能夠編譯通過,但是在執(zhí)行時(shí)便會(huì)出錯(cuò)。原因在于one和another兩個(gè)表達(dá)式雖然都是同樣的形式(Expression<Func<T, bool>>),但是它們的“參數(shù)”不是同一個(gè)對(duì)象。也就是說,one.Body和another.Body并沒有公用一個(gè)ParameterExpression實(shí)例,于是我們無論采用哪個(gè)表達(dá)式的參數(shù),在Expression.Lambda方法調(diào)用的時(shí)候,都會(huì)告訴您新的body中的某個(gè)參數(shù)對(duì)象并沒有出現(xiàn)在參數(shù)列表中。
于是,我們?nèi)绻獙?shí)現(xiàn)And和Or,做的第一件事情便是統(tǒng)一兩個(gè)表達(dá)式樹的參數(shù),于是我們準(zhǔn)備一個(gè)ExpressionVisitor:
internal class ParameterReplacer : ExpressionVisitor {public ParameterReplacer(ParameterExpression paramExpr){this.ParameterExpression = paramExpr;}public ParameterExpression ParameterExpression { get; private set; }public Expression Replace(Expression expr){return this.Visit(expr);}protected override Expression VisitParameter(ParameterExpression p){return this.ParameterExpression;} }ExpressionVisitor幾乎是處理表達(dá)式樹這種數(shù)據(jù)結(jié)構(gòu)的不二法門,它可以用于求值,變形(其實(shí)是生成新的結(jié)構(gòu),因?yàn)楸磉_(dá)式樹是immutable的數(shù)據(jù)結(jié)構(gòu))等各種操作。例如,解決表達(dá)式樹的緩存時(shí)用它來求樹的散列值或讀寫前綴樹,快速計(jì)算表達(dá)式時(shí)用它來提取表達(dá)式樹的參數(shù),并將不同的表達(dá)式樹“標(biāo)準(zhǔn)化”為相同的結(jié)構(gòu)。
ExpressionVisitor基類的關(guān)鍵,就在于提供了遍歷表達(dá)式樹的標(biāo)準(zhǔn)方式,如果您直接繼承這個(gè)類并調(diào)用Visit方法,那么最終返回的結(jié)果便是傳入的Expression參數(shù)本身。但是,如果您覆蓋的任意一個(gè)方法,返回了與傳入時(shí)不同的對(duì)象,那么最終的結(jié)果就會(huì)是一個(gè)新的Expression對(duì)象。ExpressionVisitor類中的每個(gè)方法都負(fù)責(zé)一類表達(dá)式,也都都遵循了類似的原則:它們會(huì)遞歸地調(diào)用Visit方法,如果Visit返回新對(duì)象,那么它們也會(huì)構(gòu)造新對(duì)象并返回。
ParameterReplacer類的作用是將一個(gè)表達(dá)式樹里的所有ParameterExpression替換成我們指定的新對(duì)象,因此只需覆蓋VisitParameter方法就可以了。有了它之后,And和Or方法的實(shí)現(xiàn)輕而易舉:
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another) {var candidateExpr = Expression.Parameter(typeof(T), "candidate");var parameterReplacer = new ParameterReplacer(candidateExpr);var left = parameterReplacer.Replace(one.Body);var right = parameterReplacer.Replace(another.Body);var body = Expression.And(left, right);return Expression.Lambda<Func<T, bool>>(body, candidateExpr); }public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another) {var candidateExpr = Expression.Parameter(typeof(T), "candidate");var parameterReplacer = new ParameterReplacer(candidateExpr);var left = parameterReplacer.Replace(one.Body);var right = parameterReplacer.Replace(another.Body);var body = Expression.Or(left, right);return Expression.Lambda<Func<T, bool>>(body, candidateExpr); }于是,我們最終構(gòu)造得到的Expression<Func<T, bool>>對(duì)象便可以傳入一個(gè)LINQ Provider,最終得到查詢結(jié)果:
Expression<Func<int, bool>> f = i => i % 2 == 0; f = f.Not().And(i => i % 3 == 0).Or(i => i % 4 == 0);foreach (var i in new int[] { 1, 2, 3, 4, 5, 6 }.AsQueryable().Where(f)) {Console.WriteLine(i); }輸出的結(jié)果是3和4。
這種做法是非常有實(shí)用價(jià)值的。因?yàn)橛辛薒INQ,因此許多朋友都會(huì)選擇在數(shù)據(jù)訪問層暴露一個(gè)這樣的方法給上層調(diào)用:
class ProductDao {public Product GetProduct(Expression<Func<Product, bool>> predicate){...} }但是您有沒有想過這么做的缺點(diǎn)是什么呢?這么做的缺點(diǎn)便是“過于自由”。由于GetProduct方法只將參數(shù)限制為一個(gè)Expression<Func<Product, bool>>對(duì)象,因此在調(diào)用的時(shí)候,我們可以使用任意的形式進(jìn)行傳遞。因此,外層完全有可能傳入一個(gè)目前LINQ Provider不支持的表達(dá)式樹形式,也完全有可能傳入一個(gè)雖然支持,但會(huì)導(dǎo)致查詢速度慢,影響項(xiàng)目整體性能的表達(dá)式樹。前者要在運(yùn)行時(shí)才拋出異常,而后者則引發(fā)的性能問題則更難發(fā)現(xiàn)。因此我認(rèn)為,數(shù)據(jù)訪問層不應(yīng)該如此自由,它要做出限制。而限制的方式,便是使用Query Object模式,讓GetProduct方法接受一個(gè)受限的Criteria對(duì)象:
public abstract class ProductCriteria {internal ProductCriteria(Expression<Func<Product, bool>> query){this.Query = query;}public Expression<Func<Product, bool>> Query { get; private set; } }class ProductDao {public Product GetProduct(ProductCriteria predicate){...} }而在使用時(shí),我們只提供有限的幾種條件,如:
public class ProductIdEqCriteria : ProductCriteria {public ProductIdEqCriteria(int id): base(p => p.ProductID == id){ } }public class ProductViewRangeCriteria : ProductCriteria {public ProductViewRangeCriteria(int min, int max): base(p => p.ViewCount > min && p.ViewCount < max){ } }再加上配套的擴(kuò)展方法用于And,Or,Not,于是一切盡在掌握。現(xiàn)在再去瞅瞅原Query Object模式中復(fù)雜的實(shí)現(xiàn),您是否會(huì)有一種滿足感?
轉(zhuǎn)載于:https://www.cnblogs.com/JeffreyZhao/archive/2009/09/29/specification-pattern-in-csharp-practice-answer-2.html
總結(jié)
以上是生活随笔為你收集整理的趣味编程:C#中Specification模式的实现(参考答案 - 下)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OGC之路(1) 之 WMS标准学习总结
- 下一篇: 找不到using System.Web.