趣味編程:C#中Specification模式的實現(xiàn)(參考答案 - 上)
2009-09-28 10:34 Jeffrey Zhao 閱讀(13670) 評論(12) 收藏 舉報Specification模式的作用是構(gòu)建可以自由組裝的業(yè)務(wù)邏輯元素。不過就上篇文章的示例來看,“標準”的Specification模式的實現(xiàn)還是比較麻煩的,簡單的功能也需要較復雜的代碼。不過,既然說是“標準”的方式,自然就是指可以在任意面向?qū)ο笳Z言中使用的實現(xiàn)方式,不過我們使用的是C#,在實際開發(fā)過程中,我們可以利用C#如今的強大特性來實現(xiàn)出更容易使用,更輕量級的Specification模式。
當然,有利也有弊,在使用“標準”還是“輕量級”的問題上,還要根據(jù)你的需求來進行選擇。
Specification模式的關(guān)鍵在于,Specification類有一個IsSatisifiedBy函數(shù),用于校驗?zāi)硞€對象是否滿足該Specification所表示的條件。多個Specification對象可以組裝起來,并生成新Specification對象,這便可以形成高度可定制的業(yè)務(wù)邏輯。從中可以看出,一個Specification對象的關(guān)鍵,其實就是一個IsSatisifiedBy方法的邏輯。每種對象,一段邏輯。每個對象的唯一關(guān)鍵,也就是這么一段邏輯。因此,我們完全可以構(gòu)造這么一個“通用”的類型,允許外界將這段邏輯通過構(gòu)造函數(shù)“注入”到Specification對象中:
public class Specification<T> : ISpecification<T> { private Func<T, bool> m_isSatisfiedBy; public Specification(Func<T, bool> isSatisfiedBy) { this.m_isSatisfiedBy = isSatisfiedBy; } public bool IsSatisfiedBy(T candidate) { return this.m_isSatisfiedBy(candidate); } }
嗯嗯,這也是一種依賴注入。在普通的面向?qū)ο笳Z言中,承載一段邏輯的最小單元只能是“類”,只是我們說,某某類中的某某方法就是我們需要的邏輯。而在C#中,從最早開始就有“委托”這個東西可用來承載一段邏輯。與其為每種情況定義一個特定的Specification類,讓那個Spcification類去訪問外部資源(即建立依賴),不如我們將這個類中唯一需要的邏輯給準備好,各種依賴直接通過委托由編譯器自動保留,然后直接注入到一個“通用”的類中。很關(guān)鍵的是,這樣在編程方面也非常容易。
至于原本ISpecification<T>中的And,Or,Not方法,我們可以將它們提取成擴展方法。有朋友說,既然有了擴展方法,那么對于一些不需要訪問私有成員/狀態(tài)的方法,都應(yīng)該提取到實體的外部,避免“污染”實體。不過我不同意,在我看來,到底是用實例方法還是擴展方法,還是個根據(jù)職責和概念而一定的。我在這里打算使用擴展的目的,是因為And,Or,Not并非是一個Specification對象的邏輯,并不是一個Specification對象說,“我要去And另一個”,“我要去Or另一個”,或者“我要造……取反”。就好比二元運算符&&、||、或者+、-,左右兩邊的運算數(shù)字有主次之分嗎?沒有,它們是并列的。因此,我選擇使用額外的擴展方法,而不是將這些職責交給某個Specification對象:
public static class SpecificationExtensions { public static ISpecification<T> And<T>( this ISpecification<T> one, ISpecification<T> other) { return new Specification<T>(candidate => one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate)); } public static ISpecification<T> Or<T>( this ISpecification<T> one, ISpecification<T> other) { return new Specification<T>(candidate => one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate)); } public static ISpecification<T> Not<T>(this ISpecification<T> one) { return new Specification<T>(candidate => !one.IsSatisfiedBy(candidate)); } }
此外,使用擴展方法的好處在于,如果我們想要加一個邏輯運算(如“異或”),那么是不需要修改接口的。修改接口是一件勞民傷財?shù)氖虑?/a>。
至此,我們使用Specification對象就容易多了,因為不需要為每段邏輯創(chuàng)建一個獨立的ISpecification<T>類型。但是,其實還有更簡單的:直接使用委托。既然整個Specificaiton對象的邏輯可以使用一個委托直接表示,那為什么我們還需要一個“外殼”呢?不如直接使用這樣的委托類型:
public delegate bool Spec<T>(T candicate);
當然,您也可以直接使用Func<T, bool>。我在這里創(chuàng)建Spec的目的,是因為我想“明確”這里其實是一個Specification,而不是一個普通的“接受T作為參數(shù),返回bool的方法”。于是現(xiàn)在,我們便可以用這樣的擴展方法來編寫And,Or和Not:
public static class SpecExtensions { public static Spec<T> And<T>(this Spec<T> one, Spec<T> other) { return candidate => one(candidate) && other(candidate); } public static Spec<T> Or<T>(this Spec<T> one, Spec<T> other) { return candidate => one(candidate) || other(candidate); } public static Spec<T> Not<T>(this Spec<T> one) { return candidate => !one(candidate); } }
用它來編寫上次的示例便容易多了:
static Spec<int> MorePredicate(Spec<int> original) { return original.Or(i => i > 0); } static void Main(string[] args) { var array = Enumerable.Range(-5, 10).ToArray(); var oddSpec = new Spec<int>(i => i % 2 == 1); var oddAndPositiveSpec = MorePredicate(oddSpec); foreach (var item in array.Where(i => oddAndPositiveSpec(i))) { Console.WriteLine(item); } }
由于有C#的擴展方法和委托,在C#中使用Specification模式比之前要容易許多。不過,在某些時候,我們可能還是需要老老實實按照標準來做。創(chuàng)建獨立的Specification對象的好處是在一個單獨的地方內(nèi)聚地封裝了一段邏輯,因此適合較集中,較“重”的邏輯,而“委托”則適合輕便的實現(xiàn)。委托的另一個優(yōu)勢是使用方便,但它的缺點便是難以“靜態(tài)表示”。如果您在使用Specification模式時,需要根據(jù)外部配置來決定進行何種組裝,那么可能只有為每種邏輯創(chuàng)建獨立的Specification對象了。此外,使用委托還有一個“小缺點”,即它可能會“不自覺”地提升對象的生命周期,可能會形成一些延遲方面的陷阱。
當然,我并不是說獨立Specification對象就不會造成生命周期延長——只要功能實現(xiàn)一樣,各方面也應(yīng)該是相同的。只不過獨立的Specificaiton對象給人一種“正式”而“隆重”的感覺,容易讓人警覺,因而緩解了這方面問題。
不過還有一個問題我們還沒有解決——我們現(xiàn)在組裝的是委托或Specification對象,但如果我們需要組裝一個表達式樹,組裝完畢后交給如LINQ to SQL使用,又該怎么做呢?我們的“下”便會設(shè)法解決這個問題。
浙公網(wǎng)安備 33010602011771號