<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # [.NET領域驅動設計實戰系列]專題三:前期準備之規約模式(Specification Pattern) ## 一、前言 在專題二中已經應用DDD和SOA的思想簡單構建了一個網上書店的網站,接下來的專題中將會對該網站補充更多的DDD的內容。本專題作為一個準備專題,因為在后面一個專題中將會網上書店中的倉儲實現引入規約模式。本專題將詳細介紹了規約模式。 ## 二、什么是規約模式 講到規約模式,自然想到的是什么是規約模式呢?從名字上看,**規約模式就是一個約束條件**,我們在使用倉儲進行查詢的時候,這時候就會牽涉到很多查詢條件,例如名字包含C#的書名等條件。這樣就自然需要引入[規約模式](http://en.wikipedia.org/wiki/Specification_pattern)了。規約模式的作用可以自由組裝業務邏輯元素。Specification類有一個IsSatisifiedBy函數,用于校驗某個對象是否滿足該Specification所表達的條件。多個Specification對象可以組裝起來,生成新的Specification對象,這樣可以通過組裝的方式來定制新的條件。簡單地說,規約模式就是對查詢條件表達式用類的形式進行封裝。那這樣的話,規約模式引入有什么作用呢? ## 三、為什么需要引入規約模式模式 上面只是簡單介紹了規約模式的作用——可以自由組裝業務邏輯元素。這樣文字表述未免枯燥了點,下面通過一個具體例子來說明下。 對于在倉儲中,我們經常會定義下面的接口 接下來就是實現這個接口,并在類中分別實現接口中的方法。這樣設計的好處就是一目了然,可以方便地看到Product倉儲到底提供了哪些功能。 對于這種設計,對于簡單系統并且今后擴展的可能性不大,那么這樣的設計非常合適,因為其簡潔高效。但如果你正在設計一個中大型系統,那么,針對上面的設計,你就需要考慮下面的問題了: 1. 今后如果需要添加新的查詢邏輯,結果一大堆相關代碼都需要修改,上面的設計能便于擴展嗎? 2. 由于業務的擴展,上面的設計會導致接口變得越來越大,團隊成員可能會對這個接口進行修改,添加新的接口方法。 規約模式就是DDD引入解決上面問題的一種模式。下面讓我們來看看規約模式的定義與實現。 ## 四、規約模式的傳統實現 首先來看下規約模式的類結構圖: ![specification-pattern-uml](https://box.kancloud.cn/2016-01-23_56a2eb4b88422.png) 上圖是摘自維基百科里面的,通過設計圖我們很容易實現規約模式,這樣之所以稱為的傳統實現,因為后面會對該實現應用C#的特性來對該實現進行簡化,使其更加簡單輕量。首先我們需要定義一個ISpecification接口,在接口中定義四個方法:And、Not、Or和IsSatifiedBy方法,具體接口的定義如下所示: ``` // 規約接口的定義 public interface ISpecification<T> { bool IsSatisfiedBy(T candidate); ISpecification<T> And(ISpecification<T> specification); ISpecification<T> Or(ISpecification<T> specification); ISpecification<T> Not(ISpecification<T> specification); } ``` 實現了ISpeification的對象意味著是一個Specification,即一種篩選條件,我們可以與其他Specification對象通過And、Or和Not操作來生成新的邏輯,即組合成新的篩選條件,為了方便“組合邏輯”的實現,這里還需要定義一個抽象的CompositeSpecification類: ``` // 因為And,OR和Not方法在所有的Specification都需要實現,只有IsSatisfiedBy方法才依賴業務規則 // 所以為了復用,定義一個抽象類來實現And,Or和And操作,并且留IsSatisfiedBy方法給子類去實現,所以定義其為abstract public abstract class CompositeSpecification<T>: ISpecification<T> { public abstract bool IsSatisfiedBy(T candidate); public ISpecification<T> And(ISpecification<T> specification) { return new AndSpecification<T>(this, specification); } public ISpecification<T> Or(ISpecification<T> specification) { return new OrSpecification<T>(this, specification); } public ISpecification<T> Not(ISpecification<T> specification) { return new NotSpecification<T>(specification); } } ``` CompositeSpecification提供了構建符合Specification的基礎邏輯,它提供了And、Or和Not方法的實現,讓其他Specification類只需要專注于IsSatisfiedBy方法的實現即可(這里有點模板方法模式的影子)。下面是And、Or和Not規約的具體實現: ``` // AndSpecification,OrSpecification and NotSpecification主要為了組合 **public class AndSpecification<T> : CompositeSpecification<T>** { private readonly ISpecification<T> _lefSpecification; private readonly ISpecification<T> _rightSpecification; public AndSpecification(ISpecification<T> left, ISpecification<T> right) { this._lefSpecification = left; this._rightSpecification = right; } public override bool IsSatisfiedBy(T candidate) { return this._lefSpecification.IsSatisfiedBy(candidate) && this._rightSpecification.IsSatisfiedBy(candidate); } } **public class OrSpecification<T> : CompositeSpecification<T>** { private readonly ISpecification<T> _leftSpecification; private readonly ISpecification<T> _rightSpecification; public OrSpecification(ISpecification<T> left, ISpecification<T> right) { this._leftSpecification = left; this._rightSpecification = right; } public override bool IsSatisfiedBy(T candidate) { return _leftSpecification.IsSatisfiedBy(candidate) || _rightSpecification.IsSatisfiedBy(candidate); } } **public class NotSpecification<T> : CompositeSpecification<T>** { private readonly ISpecification<T> _specification; public NotSpecification(ISpecification<T> specification) { this._specification = specification; } public override bool IsSatisfiedBy(T candidate) { return !_specification.IsSatisfiedBy(candidate); } } ``` 接下來我們可以定義具體的規約模式,如果IdEqualSpecification、NameEqualSpecification規約等。下面就看下引入規約模式后,是如何解決上面倉儲接口設計所存在的問題的。 ``` // 引入規約模式,IProductRespository接口的定義 public interface IProductRespository { Product GetBySpecification(ISpecification<Product> spec); IEnumerable<Product> FindBySpecification(ISpecification<Product> spec); } public class IdEqualSpecification : CompositeSpecification<Product> { private readonly Guid _id; public IdEqualSpecification(Guid id) { _id = id; } public override bool IsSatisfiedBy(Product candidate) { return candidate.Id.Equals(_id); } } public class NameEqualSpecification : CompositeSpecification<Product> { private readonly string _name; public NameEqualSpecification(string name) { _name = name; } public override bool IsSatisfiedBy(Product candidate) { return candidate.Name.Equals(_name); } } public class NewProductsSpecification : CompositeSpecification<Product> { public override bool IsSatisfiedBy(Product candidate) { return candidate.IsNew == true; } } ``` 通過引入規約后,Product倉儲中所有特定用途的操作都刪除了,取而代之的是2個非常簡潔的方法。規約模式解耦了倉儲操作和篩選條件,如果業務擴展,我們可以定制我們的Specification,并將其注入到倉儲即可。倉儲的接口和實現無需任何修改。 下面通過一個具體的演示例子來看下傳統規約模式的應用。具體的場景是這樣的:我們想篩選一批int數組中的偶數和大于0的數字出來。因為這里涉及2個篩選條件,一個是偶數,一個是大于0的數,這樣我們就可以通過定義偶數規約和正數規約。具體的實現如下所示: ``` // 具體規約,偶數規約 public class EvenSpecification : CompositeSpecification<int> { public override bool IsSatisfiedBy(int candidate) { return candidate % 2 == 0; } } // 具體的規約,正數規約 public class PlusSpecification : CompositeSpecification<int> { public override bool IsSatisfiedBy(int candidate) { return candidate > 0; } } ``` 接下來通過And操作和將2中規約組合起來形成新的規約。具體的測試代碼如下所示: ``` using spec1 =SpecificationPatternDemo.Specification; class Program { static void Main(string[] args) { Demo1(); Console.Read(); } public static void Demo1() { var items = Enumerable.Range(-5, 10); Console.WriteLine("產生的數組為:{0}", string.Join(", ", items.ToArray())); spec1.ISpecification<int> evenSpec = new spec1.EvenSpecification(); // 獲得一個組合規約 var compositeSpecification = GetCompositeSpecification(evenSpec); // 類似Where(it=>it%2==0 && it > 0) // 前者是把兩個條件合并寫死成一個條件,而后者是將其組合成一個新條件。就如拼圖出一架飛機和直接制造一個飛機模型概念是完全不同的 foreach (var item in items.Where(it=>compositeSpecification.IsSatisfiedBy(it))) { // 輸出既是正數又是偶數的數 Console.WriteLine(item); } } private static spec1.ISpecification<int> GetCompositeSpecification(spec1.ISpecification<int> spec) { spec1.ISpecification<int> plusSpec = new spec1.PlusSpecification(); return spec.And(plusSpec); } } ``` 具體的運行結果如下圖所示: ![](https://box.kancloud.cn/2016-01-23_56a2eb4b992ee.png) 上面我們已經介紹完規約模式的實現,并且通過對比的方式來介紹引入規約模式所解決之前的問題。但是傳統規約模式的實現顯得非常臃腫。因為你想實現一個新的規約,你需要新增一個新的Specification類,這樣下來,我們的項目中必然會堆積大量的Specification類。有些規約可能只只使用了一次。這就好比.NET里的委托方法一樣,為了解決類似的問題,.NET引入了匿名方法和lamada表達式。同樣,我們借助C#的特性也可以使得傳統規約模式的實現更輕量。下面就具體看下規約模式的輕量實現是如何去實現的。 ## 五、規約模式的輕量實現 從上面可以看出,規約模式的關鍵在于IsSatisifiedBy函數,該函數用于校驗某個對象是否滿足該規約所表示的條件,IsSatisifiedBy函數的返回類型為bool類型,這樣我們完全可以讓一個ISpecification只具有IsSatisifiedBy函數。然后該函數返回一個委托調用結果。至于原本ISpecification中的And、Or和Not方法,我們將它們提起成擴展方法。經過上面的分析,輕量化后的規約模式實現也就出來了。具體實現如下所示: ``` public interface ISpecification<in T> { bool IsSatisfiedBy(T candidate); } public class Specification<T> : ISpecification<T> { private readonly Func<T, bool> _isSatisfiedBy; public Specification(Func<T, bool> isSatisfiedBy) { this._isSatisfiedBy = isSatisfiedBy; } public bool IsSatisfiedBy(T candidate) { return _isSatisfiedBy(candidate); } } public static class SpecificationExtensions { public static ISpecification<T> And<T>(this ISpecification<T> left, ISpecification<T> right) { return new Specification<T>(candidate => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate)); } public static ISpecification<T> Or<T>(this ISpecification<T> left, ISpecification<T> right) { return new Specification<T>(candidate => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate)); } public static ISpecification<T> Not<T>(this ISpecification<T> one) { return new Specification<T>(candidate => !one.IsSatisfiedBy(candidate)); } } ``` 使用擴展方法的好處在于,如果我們要加一個邏輯運行,如異或,那么就不需要修改接口了。修改接口是一個不推薦的的事情。因為接口修改會破壞之前已經發布的接口實現。因此,一旦接口發布之后,它就不能被修改了。這意味著,我們在定義接口時應該仔細推敲,做到接口的職責應該尤其單一。 輕量的實現使得使用Specification對象容易多了,我們不需要為每段邏輯創建一個獨立的Specification類。下面具體看下規約模式的輕量實現的使用示例: ``` using SpecificationPatternDemo.Specification_2; using spec2 = SpecificationPatternDemo.Specification_2; class Program { static void Main(string[] args) { Demo2(); Console.Read(); } public static void Demo2() { var items = Enumerable.Range(-5, 10); Console.WriteLine("產生的數組為:{0}", string.Join(", ", items.ToArray())); spec2.ISpecification<int> evenSpec = new spec2.Specification<int>(it => it % 2 == 0); var compositeSpec = GetCompositeSpecification2(evenSpec); foreach (var i in items.Where(it => compositeSpec.IsSatisfiedBy(it))) { Console.WriteLine(i); } } private static spec2.ISpecification<int> GetCompositeSpecification2(spec2.ISpecification<int> spec) { spec2.ISpecification<int> plusSpec = new spec2.Specification<int>(it => it > 0); return spec.And(plusSpec); } } ``` 從上面的例子可以看出,此時并不需要定義單獨的Specification對象了,只需要用委托來代替即可。其運行結果與上面傳統實現一樣。其實,還可以更簡單,我們可以直接使用一個委托,而不不需要定義ISpecification接口和其Specification實現。其實現方式如下所示: ``` // 更輕量的實現 public static class SpecExtensitions { public static Func<T, bool> And<T>(this Func<T, bool> left, Func<T, bool> right) { return candidate => left(candidate) && right(candidate); } public static Func<T, bool> Or<T>(this Func<T, bool> left, Func<T, bool> right) { return candidate => left(candidate) || right(candidate); } public static Func<T, bool> Not<T>(this Func<T, bool> one) { return candidate => !one(candidate); } } ``` 上面的實現,我們就只需要一個擴展方式就可以了,其使用示例代碼如下所示: ``` class Program { static void Main(string[] args) { Demo3(); Console.Read(); } public static void Demo3() { var items = Enumerable.Range(-5, 10); Console.WriteLine("產生的數組為:{0}", string.Join(", ", items.ToArray())); Func<int, bool> evenSpec = it => it % 2 == 0; var compositeSpec = GetCompositeSpec(evenSpec); foreach (var i in items.Where(it => compositeSpec(it))) { Console.WriteLine(i); } } private static Func<int, bool> GetCompositeSpec(Func<int, bool> spec) { return spec.And(it => it > 0); } } ``` ## 六、規約模式的輕量實現的完善——對Linq查詢支持 上面輕量級的Specification模式拋棄了具體的Specification類型,而是使用一個委托對象關鍵的IsSatisfiedBy方法。其優勢在于使用簡單,但是該實現不能支持Linq查詢或表達式的場景。因為EF中的DbContext.Dbset集合的進行where篩選參數只能是表達式樹。所以我們不能用委托對象來判斷邏輯,取而代之的使用表達式樹。對于表達式樹的構造主要由參數和主體構造,所以針對于Not方法可以如下的方式來實現: ``` 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); } } ``` 對于Not方法,我們只要獲取它的參數表達式,再將它的Body外包一個Not表達式,便可以此構造一個新的表達式了。但And和Or方法實現不能像Not一樣簡單處理: ``` // 不能這么處理 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); } ``` 因為one和another兩個表達式雖然都是同樣的形式(Expression&lt;Func&lt;T, bool&gt;&gt;),但是它們的“參數”不是同一個對象。即one.Body和another.Body并沒有公用一個ParameterExpression實例,于是無論采用哪個表達式的參數,在Expression.Lambda方法調用的時候,都會出現body中的某個參數對象并沒有出現在參數列表中的錯誤。 既然參數不一致,所以要實現And和Or方法,必須統一兩個表達式樹的參數。為了達到這個目標,我們可以利用[ExpressionVisitor](https://msdn.microsoft.com/zh-cn/library/system.linq.expressions.expressionvisitor(v=vs.110).aspx)類來實現。這里定義一個派生于[ExpressionVisitor](https://msdn.microsoft.com/zh-cn/library/system.linq.expressions.expressionvisitor(v=vs.110).aspx)的類。具體實現如下: ``` 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; } } ``` Expressionvistor可以用于求值、變形等各種操作。它提供了遍歷表達式樹的標準方式,如果你直接繼承這個類并調用Visit方法(如上面Replace方法的實現一樣),那么最終返回的結果便是傳入的Expresssion參數本身。但是,如果你覆蓋任意一個方法,返回了與傳入時不同的對象,那么最終的結果就是一個新的Expression對象。就如上面VisitParameter方法實現一樣。它直接返回我們定義的ParameterExpression對象。 通過上面分析,ParameterExpression類的作用是將一個表達式里的所有ParameterExpression替換成我們指定的新對象,這樣就可以解決之前參數不一致的情況。所以我們And和Or方法的實現就是將兩個表達式樹參數替換成我們首先定義好的參數表達式。具體的實現方式如下所示: ``` 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); } public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another) { // 首先定義好一個ParameterExpression var candidateExpr = Expression.Parameter(typeof (T), "candidate"); var parameterReplacer = new ParameterReplacer(candidateExpr); // 將表達式樹的參數統一替換成我們定義好的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); } } ``` 到此,我們就完成了規約模式對Linq支持的輕量實現了。下面讓我們看看上面輕量實現是如何調用的呢?具體調用代碼如下: ``` class Program { private static void Main(string[] args) { Demo1(); Console.Read(); } public static void Demo1() { var items = Enumerable.Range(-5, 10); Console.WriteLine("產生的數組為:{0}", string.Join(", ", items.ToArray())); Expression<Func<int, bool>> f = i => i % 2 != 0; f = f.Not().And(i => i > 0); // 通過AsQueryable成IQueryable<int>,因為IQueryable<T>的Where方法的參數要求是表達式樹 foreach (var i in items.AsQueryable().Where(f)) { Console.WriteLine(i); } } } ``` 其運行結果與前面的例子中的運行結果一樣,一樣成功返回了即是偶數又是正數的集合。 ## 七、總結 到這里,規約模式的實現就結束了,后期將會在網上書店的案例中引入規約模式,dax.net的Byteart Retail案例中規約模式的實現即包括了傳統實現,也包括了對Linq支持的輕量實現。開始我認為傳統實現是多余的,因為你已經有了規約模式的輕量實現了,何必又有傳統實現呢?這不是包括兩種實現嗎?后面仔細想想,這樣設計也有其存在的道理,因為對于一些邏輯復雜的規約實現,我們可以新建一個具體的規約類,但對于一些簡單和僅使用一次的規約邏輯,就可以直接用表達式樹來代替,就不需要單獨為該段邏輯單獨新建一個具體的規約類。這樣的實現就如同,有了匿名方法和Lambda表達式,是不是委托就可以不需要了。顯然不是的,所以我在我的網上書店案例中也將會引入這兩種實現,讓用戶可以靈活選擇這兩種方式。在下一專題,我繼續介紹一個前期準備的內容,即工作單元模式(Unit Of Work,即UOW)。 本專題的所有源碼下載:[SpecificationPatternDemo.zip](http://files.cnblogs.com/files/zhili/SpecificationPatternDemo.zip)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看