當然,您也可以直接使用 Func<T, bool>。我在這裡建立 Spec 委託的目的,是為了「明確」表達這裡其實是一個 Specification,而非單純的「接受 T 參數並回傳 bool 的方法」。有了這個定義,我們便可以撰寫 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 模式比以往容易許多。不過,某些場合我們仍需依循標準實現。建立獨立 Specification 物件的好處在於能在單一處內聚封裝一段邏輯,適合較集中、較「重」的邏輯;而「委託」則適合輕量實現。委託的另一優勢是使用方便,但缺點是難以「靜態表示」。若您在使用 Specification 模式時,需要根據外部配置決定組裝方式,那麼可能只能為每種邏輯建立獨立的 Specification 物件。此外,委託還有一個「小缺點」:它可能會「不自覺」地延長物件生命週期,形成一些延遲方面的陷阱。
當然,我並非說獨立 Specification 物件不會造成生命週期延長——功能實現相同,各方面也應相同。只是獨立的 Specification 物件給人一種「正式」而「隆重」的感覺,容易讓人警覺,從而緩解這方面問題。
不過還有一個問題未解決——我們目前組裝的是委託或 Specification 物件,但若需要組裝表達式樹並交給 LINQ to SQL 使用,又該怎麼辦?下篇將設法解決這個問題。