青梅煮酒論C#:Specification pattern
2011-01-16 13:56 空逸云 閱讀(3316) 評論(17) 收藏 舉報Specification模式早在3個多月前,閱讀一個開源博客的時候便接觸到了.但并沒多少深入.最近,抽空將其好好研究了一番,果然,其魅力的確能讓你回味無窮,那現在,就讓我們零距離接觸Specification模式吧!
何為Specification?
Specification,中文有翻譯規格,雖然有很多爭論,但目前叫得最多的,還是規格模式,既然說到了規格,那其作用估計就能不言而喻了.
Specification模式的作用是構建可以自由組裝的業務邏輯元素.
上面這句話可以完整的概括Specification的作用,但第一次理解或許會有些許困難,沒關系,可以這么理解,Specification模式起作用的,是每個Specification類,每一個Specification類,可以看成一張"規格書",或者說是某種"規格約束",而通過這張"規格書"或者"規格約束",我們能得到我們所想要的這種"規格"的對象,并且,不同的規格是可以組合起來,形成新的規格.如果還不明白.好吧,您知道樂高積木吧?我們形象的把每個規格想象成一個不同的樂高模型,而不同的樂高模型組合在一起了,就形成了新的模型,以達到我們所需的結果.

如何實現Specification
了解了什么是Specification,那接下去,您肯定會問,該怎么實現?其實很簡單,Specification模式里面只有一個方法,我們有了"規格書",下一步,當然就是從不同的對象中得到符合"規格書"的對象,那這個匹配的過程就形成了一個動作抽象.IsSatisfiedBy就是幫助你從"對象堆"中找到你所需"規格"的動作.如此.我們可以實現的Specification接口如下
public interface ISpecification<T> { bool IsSatisfiedBy(T candidate); }
但是,一般的Specification都提供了幾個基本的組合操作,例如Add,Or,Not,可以利用它們分別組合成一個新的Specification.經過初步改動,最后ISpecification如下
public interface ISpecification<T> { bool IsSatisfiedBy(T candidate); ISpecification<T> Add(ISpecification<T> other); ISpecification<T> Or(ISpecification<T> other); ISpecification<T> Not(); }
為了重用可復用邏輯,我們定義了一個抽象類CompositeSpecification
public abstract class CompositeSpecification<T> : ISpecification<T> { public abstract bool IsSatisfiedBy(T candidate); public ISpecification<T> Add(ISpecification<T> other) { return new AddSpecification<T>(this, other); } public ISpecification<T> Or(ISpecification<T> other) { return new OrSpecification<T>(this, other); } public ISpecification<T> Not() { return new NotSpecification<T>(this); } }
并定義了基本的幾個"組合"類
public class AddSpecification<T> : CompositeSpecification<T> { private ISpecification<T> one; private ISpecification<T> other; public AddSpecification(ISpecification<T> one, ISpecification<T> other) { this.one = one; this.other = other; } public override bool IsSatisfiedBy(T candidate) { return one.IsSatisfiedBy(candidate) && other.IsSatisfiedBy(candidate); } } public class OrSpecification<T> : CompositeSpecification<T> { private ISpecification<T> one; private ISpecification<T> other; public OrSpecification(ISpecification<T> one, ISpecification<T> other) { this.one = one; this.other = other; } public override bool IsSatisfiedBy(T candidate) { return one.IsSatisfiedBy(candidate) || other.IsSatisfiedBy(candidate); } } public class NotSpecification<T> : CompositeSpecification<T> { private ISpecification<T> one; public NotSpecification(ISpecification<T> one) { this.one = one; } public override bool IsSatisfiedBy(T candidate) { return !one.IsSatisfiedBy(candidate); } }
最后,我們定義我們所需要的規格,在這里,我們定義一個基數規格與正數規格
public class OddSpecification : ComsptionSpecification<int> { public override bool IsSatisfiedBy(int candidate) { return candidate % 2 != 0; } } public class PlusSpecification : ComsptionSpecification<int> { public override bool IsSatisfiedBy(int candidate) { return candidate > 0; } }
現在,萬事俱備,只欠東風.就差你來調用了.
static void Main(string[] args) { var items = Enumerable.Range(-5, 10); ISpecification<int> oddSpec = new OddSpecification(); var spec = MoreCandidate(oddSpec); foreach (var i in items.Where(it => spec.IsSatisfiedBy(it))) { Console.WriteLine(i); } } static ISpecification<int> MoreCandidate(ISpecification<int> spec) { ISpecification<int> plusSpec = new PlusSpecification(); return spec.Or(plusSpec); }
這個邏輯還是十分簡單的.我們先定義了一個基數規格,后面我們發現這個規格不足以得到我們所需要的,我們想OrWhere它是正數.于是,我們"組合"成了一個新的規格.結果
這里應該注意的是.在這里我們提供了一種OrWhere功能,這是在一條條件的基礎上再匹配多另一條條件,有同學說直接Where(it=>it%2!==0||it>0)就可以了.必須指明的是,后者就變成了一個條件.你把兩個條件合并寫死成一個條件了.不是將其"組合"成一個新條件.就像你用樂高堆砌出一架跑車模型和直接澆鑄出一架跑車模型的概念是完全不同的.
優雅實現
雖然我們完美的實現了Specification模式,但是,聰明的你應該能看到它的弊病了.就是傳統的Specification模式實在是太"重量級"了,如果你想實現一個新的"規格",那么你必須需要新增一個新的Specification類.這樣下來,最終我們的類庫中必然堆積了許許多多的Specification類.既然如此,有沒有什么方法可以讓其的實現變得更加的輕易?答案是肯定的.我們再一次解析Specification模式,其重點是IsSatisfiedBy方法.遵守我們一貫的思路,改進必然是在重點實現處.于是,一種優雅的實現便誕生了.
public interface ISpecification<T> { bool IsSatisfiedBy(T candidate); }
我們還是必須定義一個ISpecification接口.與前面不同的是,該接口里不再有Add,Or,Not等方法.為何?后面講解.
public class Specification<T> : ISpecification<T> { private Func<T, bool> m_isSatisfiedBy; public bool IsSatisfiedBy(T candidate) { return m_isSatisfiedBy(candidate); } public Specification(Func<T, bool> isSatisfiedBy) { this.m_isSatisfiedBy = isSatisfiedBy; } }
接下來,我們定義了Specification<T>類繼承了ISpecification<T>接口,此處要注意的是.在其構造函數中.我們傳入了一個委托,然后,將該委托賦給私有變量m_isSatisfiedBy,最后.IsSatisfiedBy方法只是簡單的調用了該Func,這實際上了也是依賴了注入.只不過我們注入的是一種特殊的類,委托.如此一來,只要我們需要新的"規格"時,只需要傳入相應不同委托便可以實現.那么傳統實現中Add,Or,Not等組合實現該如何實現.在次我們利用C#3.5的特性--擴展方法.我們實現了一個擴展類,并將所有的"組合"方法變成擴展方法.
public static class SpecificationExtensions { public static ISpecification<T> Add<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)); } }
至于為什么要生成一個新的擴展類,并將方法提取出來.站在面向對象的角度上并不能說哪種更"面向對象",實際上,并沒有純粹意義上的"面向對象標準".只是我們習慣上會不知不覺把一些"良好的面向對象實踐"當成"標準",開發者站在不同的位置上,看到的東西,細節必然不同.如此,"面向對象"的結果也就不同.老趙說得很對(個人觀點),當我們執行Add,Or,Not等操作的時候,并不是對象本身去Add,Or,Not,一般比較而言,更多的我們不會拿自己與別人比較,這樣未免有失公正,而是有個"第三方",由它來進行比較,拿"你"和"他"進行比較.這里,我們也是借鑒現實生活的抽象,完成類的抽象.至此,這個輕量級的實現已經完成.
static void Main(string[] args) { var items = Enumerable.Range(-5, 10); ISpecification<int> oddSpec = new Specification<int>(it => it % 2 != 0); var spec = MoreCandidate(oddSpec); foreach (var i in items.Where(it => spec.IsSatisfiedBy(it))) { Console.WriteLine(i); } } static ISpecification<int> MoreCandidate(ISpecification<int> spec) { ISpecification<int> plusSpec = new Specification<int>(it => it > 0); return spec.Or(plusSpec); }
輕量級實現
即使前面的實現已經足夠"輕量級",但對于懶人而言卻往往還是不夠的.那么是否還有更"懶"的實現方法?答案是肯定的.再一想一下,Specification模式的實現其實就是IsSatisfiedBy方法的實現.那么我們直接把IsSatisfiedBy提取出來,形成一種更"懶"的實現.聰明的你應該想到了.答案就是利用委托.我們新建了一個委托.
public delegate bool Spec<T>(T candidate);
命名為Spec完全只是為了說明這是一個Specification,它接受一個參數,并返回一個布爾值.只不過這種實現是在太過于簡單.簡單到沒有提供一定的約束.所以,必須遵守一種約定,這也是約定大于約束的一種實現.和第二種實現同理,我們還實現一個SpecificationExtension類.
public static class SpecExtensitions { public static Spec<T> Add<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); } }
這樣一來.我們就實現了一套"超輕量級"的Specification實現.
static void Main(string[] args) { var items = Enumerable.Range(-5, 10); Spec<int> oddSpec = it => it % 2 != 0; var spec = MoreCandidate(oddSpec); foreach (var i in items.Where(it => spec(it))) { Console.WriteLine(i); } } static Spec<int> MoreCandidate(Spec<int> spec) { return spec.Or(it => it > 0); }
結語
Specification模式的應用能很大程度上減少我們的工作量,目前應用的比較多的是在LINQ2SQL上,查詢數據時采用不同的Specification從而獲取你所需要的數據.MVC下的Reposity+Specification能大大提高項目的擴展性.不過,世上沒有萬能的靈丹妙藥,如一把雙刃劍,能傷人,也能傷己.任何一種模式的濫用都會造成不良的影響.所以,只有該需要它的時候使用它才是最合理的.那什么時候該使用呢?當你發現你的條件實在是太過千變萬化,當你需要OrWhere,當你.....(歡迎你留下你認為合適的時候,:-) )
擴展閱讀
出處:http://kongyiyun.cnblogs.com
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。


浙公網安備 33010602011771號