.NET框架設計(常被忽視的C#設計技巧)

王清培發表於2013-07-29

閱讀目錄:

  • 1.開篇介紹
  • 2.儘量使用Lambda匿名函式呼叫代替反射呼叫(走進宣告式設計)
  • 3.被忽視的特性(Attribute)設計方式
  • 4.擴充套件方法讓你的物件如虎添翼(要學會使用擴充套件方法的設計思想)
  • 5.別怕Static屬性(很多人都怕Static在Service模式下的設計,其實要學會使用執行緒本地儲存(ThreadStatic))
  • 6.泛型的協變與逆變(設計架構介面(Interface)時要時刻注意物件的協變、逆變)
  • 7.使用泛型的型別推斷(還在為引數型別煩惱嗎)
  • 8.鏈式程式設計(設計符合大腦思維習慣的處理流程)
    • 8.1.鏈式程式設計(多條件(方法碎片化)呼叫
  • 9.部分類、部分方法的使用(擴大設計範圍)

1.】開篇介紹

本文中的內容都是我無意中發現覺得有必要分享一下的設計經驗,沒有什麼高深的技術,只是平時我們可能會忽視的一些設計技巧;為什麼有這種想法是因為之前跟一些同事交流技術的時候會發現很多設計思維被固化了,比如之前我在做客戶端框架開發的時候會去設計一些關於Validator、DTO Transfer等常用的Common function,但是發現在討論某一些技術實現的時候會被弄的雲裡霧裡的,會自我鬱悶半天,不會及時的明白對方在說的問題;

後來發現他們一是沒有把概念分清楚,比如.NETFrameworkC#VisualStudio,這三者之間的關係;二是沒有理解.NET中各個物件的本質含義,比如這裡的特性(Attribute),大部分人都認為它是被用來作為程式碼說明、標識使用的,而沒有突破這個思維限制,所以在設計一些東西的時候會繞很多彎路;還有一點是很多人對C#中的語法特性分不清版本,當然我們要大概的瞭解一下哪些特性或者語法是C#2的哪些是C#3的,這樣在我們設計東西的時候不會由於專案的版本問題而導致你無法使用設計技巧,比如擴充套件方法就無法使用在低於.NET3.0版本中,LINQ也無法在低於.NET3.O的版本中使用;

.NETFramework的版本不斷的在升級,目前差不多5.0都快面世了;.NETFramework的升級跟C#的升級沒有必然的關係,這個要搞清楚;C#是為了更好的與.NET平臺互動,它提供給我們的都是語法糖,最後都是.NETCTS中的型別;就比如大家都在寫著LINQ,其實到最後LINQ也就被自動解析成對方法的直接呼叫;

2.】儘量使用委託呼叫代替反射呼叫

委託相信大家都玩的很熟,委託的發展到目前為止是相當不錯的,從原本很繁瑣的每次使用委託的時候都需要定義一個相應的方法用來例項化委託,這點在後來的C#2中得到了改進,支援匿名委託delegate{…}的方式使用,再到現在的C#3那就更方便了,直接使用面向函式式的Lambda表示式;那麼這樣還需要反射呼叫物件的方法嗎?(當然特殊場合我們這裡不考慮,只考慮常用的場景;)當然反射不是不好,只是反射需要考慮很多效能優化方面的東西,增加了程式碼的複雜性,也讓框架變的很重(現在都是在追求輕量級,只有在DomainModel中需要將平面化的資料抽象;),所以何不使用簡單方便的委託呼叫呢;

注:如果你是初學者,這裡的委託可以理解成是我們平時常用的Lambda表示式,也可以將它與Expression<T>結合起來使用,Expression<T>是委託在執行時的資料結構,而非程式碼執行路徑;(興趣的朋友可以檢視本人的:LINQ系列文章

下面我們來看一下演示程式碼:

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-07-28
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟體工程實踐;
 6  *==============================================================================*/
 7 using System;
 8 using System.Collections.Generic;
 9 using System.Linq.Expressions;
10 using System.Linq;
11 using Infrastructure.Common.Cache;
12 using Infrastructure.Common.Validator;
13 
14 namespace ConsoleApplication1.DomainModel
15 {
16     /// <summary>
17     /// Order.
18     /// </summary>
19     [EntityCache(10, true)]
20     [EntityValidator(ValidatorOperationType.All)]
21     public class Order
22     {
23         /// <summary>
24         /// Order code.
25         /// </summary>
26         public string OrderCode { get; set; }
27 
28         /// <summary>
29         /// Items filed.
30         /// </summary>
31         private List<Item> items = new List<Item>();
32         /// <summary>
33         /// Gets items .
34         /// </summary>
35         public IEnumerable<Item> Items { get { return items; } }
36 
37         /// <summary>
38         /// Submit order date.
39         /// </summary>
40         public DateTime SubmitDate { get; set; }
41 
42         /// <summary>
43         /// Mark <see cref="DomainModel.Order"/> Instance.
44         /// </summary>
45         /// <param name="orderCode">Order code. </param>
46         public Order(string orderCode)
47         {
48             this.OrderCode = orderCode;
49         }
50 
51         /// <summary>
52         /// Sum items prices.
53         /// </summary>
54         /// <param name="itemUsingType">item type.</param>
55         /// <returns>prices .</returns>
56         public double SumPrices(int itemUsingType)
57         {
58             double resultPrices = 0.00;
59             var currentItems = items.GroupBy(item => item.ItemUsingType).Single(group => group.Key == itemUsingType);
60             if (currentItems.Count() > 0)
61             {
62                 foreach (var item in currentItems)
63                 {
64                     resultPrices += item.Price;
65                 }
66             }
67             return resultPrices;
68         }
69 
70         /// <summary>
71         /// Add item to order.
72         /// </summary>
73         /// <param name="item">Item.</param>
74         /// <returns>bool.</returns>
75         public bool AddItem(Item item)
76         {
77             if (!item.ItemCode.Equals(string.Empty))
78             {
79                 this.items.Add(item);
80                 return true;
81             }
82             return false;
83         }
84     }
85 }
View Code

這是一個訂單領域實體,它裡面引用了一個Item的商品型別;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-07-28
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟體工程實踐;
 6  *==============================================================================*/
 7 using System;
 8 
 9 namespace ConsoleApplication1.DomainModel
10 {
11     /// <summary>
12     /// Order item.
13     /// </summary>
14     public class Item
15     {
16         /// <summary>
17         /// Item code.
18         /// </summary>
19         public Guid ItemCode { get; set; }
20 
21         /// <summary>
22         /// Item price.
23         /// </summary>
24         public float Price { get; set; }
25 
26         /// <summary>
27         /// Item using type.
28         /// </summary>
29         public int ItemUsingType { get; set; }
30     }
31 }
View Code

上面程式碼應該沒有問題,基本的訂單領域模型大家都太熟了;為了保證上面的程式碼是絕對的正確,以免程式錯誤造成閱讀者的不爽,所以都會有100%的單元測試覆蓋率;這裡我們主要使用的是Order類中的SumPrices方法,所以它的UnitTest是100%覆蓋;

圖1:

Order中的SumPrices方法的UnitTest程式碼:

 1 using System;
 2 using Microsoft.VisualStudio.TestTools.UnitTesting;
 3 using NSubstitute;
 4 
 5 namespace ConsoleApplication.UnitTest
 6 {
 7     using ConsoleApplication1.DomainModel;
 8 
 9     /// <summary>
10     /// Order unit test.
11     /// </summary>
12     [TestClass]
13     public class DomainModelOrderUnitTest
14     {
15         /// <summary>
16         /// Order sumprices using type 1 test.
17         /// </summary>
18         [TestMethod]
19         public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs1_UnitTest()
20         {
21             Order testOrder = new Order(Guid.NewGuid().ToString());
22 
23             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
24             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
25 
26             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
27             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
28 
29             double result = testOrder.SumPrices(1);
30 
31             Assert.AreEqual(result, 25.0F);
32         }
33 
34         /// <summary>
35         /// Order sumprices using type is 2 test.
36         /// </summary>
37         [TestMethod]
38         public void DomainModelOrderUnitTest_SumPrices_ItemUsingTypeIs2_UnitTest()
39         {
40             Order testOrder = new Order(Guid.NewGuid().ToString());
41 
42             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
43             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
44 
45             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
46             testOrder.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
47 
48             double result = testOrder.SumPrices(2);
49 
50             Assert.AreEqual(result, 50.0F);
51         }
52     }
53 }
View Code

在以往我基本上不寫單元測試的,但是最近工作上基本上都需要寫每個方法的單元測試,而且要求是100%覆蓋,只有這樣才能保證程式碼的正確性;也建議大家以後多寫單元測試,確實很有好處,我們應該把單元測試做起來;下面我們言歸正傳;

由於我們的Order是在DomainModel Layer中,現在有一個需求就是在Infrastructure Layer 加入一個動態計算Order中指定Item.ItemUsingType的所有Prices的功能,其實也就是說需要將我們的一些關鍵資料通過這個功能傳送給遠端的Service之類的;這個功能是屬於Infrastructure中的Common部分也就是說它是完全獨立與專案的,在任何地方都可以通過它將DomainModel中的某些領域資料傳送出去,那麼這樣的需求也算是合情合理,這裡我是為了演示所以只在Order中加了一個SumPrices的方法,可能還會存在其他一些DomainModel物件,然後這些物件都有一些關鍵的業務資料需要在通過Infrastructure的時候將它們傳送出去,比如傳送給配送部門的Service Interface;

那麼常規設計可能需要將擴充套件點配置出來放在指定的配置檔案裡面,然後當物件經過Infrastructure Layer中的指定Component時觸發事件路由,然後從快取中讀取出配置的資訊執行,那麼配置檔案可能大概是這樣的一個結構:DomainEntity名稱、觸發動作、方法名稱、引數,DomainEntity名稱是確定聚合根,觸發動作是對應Infrastructure中的元件,當然你也可以放在DomainModel中;這裡只關心方法名稱、引數;

當然這裡只演示跟方法呼叫相關的程式碼,其他的不在程式碼中考慮;我們來看一下相關程式碼:

1 using System;
2 namespace Infrastructure.Common
3 {
4     public interface IBusinessService
5     {
6         void SendBusinessData(object callAuthor, string methodName, object parameterInstance);
7         void SendBusinessData<P>(Func<P, object> callFun, P parameter);
8     }
9 }
View Code

這是業務呼叫介面;

 1 using System;
 2 using System.Reflection;
 3 using System.Linq;
 4 using System.Linq.Expressions;
 5 
 6 namespace Infrastructure.Common
 7 {
 8     /// <summary>
 9     /// Business service .
10     /// </summary>
11     public class BusinessService : IBusinessService
12     {
13         /// <summary>
14         /// Send service data interface .
15         /// </summary>
16         private ISendServiceData sendService;
17 
18         /// <summary>
19         /// Mark <see cref="Infrastructure.Common.ISendServiceData"/> instance.
20         /// </summary>
21         /// <param name="sendService"></param>
22         public BusinessService(ISendServiceData sendService)
23         {
24             this.sendService = sendService;
25         }
26 
27         /// <summary>
28         /// Send business data to service interface.
29         /// </summary>
30         /// <param name="callAuthor">Object author.</param>
31         /// <param name="methodName">Method name.</param>
32         /// <param name="parameterInstance">Method call parameter.</param>
33         public void SendBusinessData(object callAuthor, string methodName, object parameterInstance)
34         {
35             object result =
36                 callAuthor.GetType().GetMethod(methodName).Invoke(callAuthor, new object[] { parameterInstance });
37             if (result != null)
38             {
39                 sendService.Send(result);
40             }
41         }
42 
43         /// <summary>
44         /// Send business data to service interface.
45         /// </summary>
46         /// <typeparam name="P"></typeparam>
47         /// <param name="callFun"></param>
48         /// <param name="parameter"></param>
49         public void SendBusinessData<P>(Func<P, object> callFun, P parameter)
50         {
51             object result = callFun(parameter);
52             if (result != null)
53             {
54                 sendService.Send(result);
55             }
56         }
57     }
58 }
View Code

這裡簡單實現IBusinessService介面,其實程式碼很簡單,第一個方法使用反射的方式呼叫程式碼,而第二個方法則使用委託呼叫;在實現類裡面還包含了一個簡單的介面;

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace Infrastructure.Common
 8 {
 9     public interface ISendServiceData
10     {
11         void Send(object sendObject);
12     }
13 }
View Code

目的是為了方便單元測試,我們來看一下單元測試程式碼;

 1 using System;
 2 using Microsoft.VisualStudio.TestTools.UnitTesting;
 3 using Infrastructure.Common;
 4 using ConsoleApplication1.DomainModel;
 5 using NSubstitute;
 6 
 7 namespace Infrastructure.Common.UnitTest
 8 {
 9     [TestClass]
10     public class InfrastructureCommonBusinsessServiceUnitTest
11     {
12         [TestMethod]
13         public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessData()
14         {
15             Order order = new Order(Guid.NewGuid().ToString());
16 
17             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
18             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
19 
20             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
21             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
22 
23             ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
24             object sendresult = null;
25             mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
26 
27             BusinessService testService = new BusinessService(mockISendServiceData);
28             testService.SendBusinessData(order, "SumPrices", 1);
29 
30             Assert.AreEqual((double)sendresult, 25);
31         }
32 
33         [TestMethod]
34         public void InfrastructureCommonBusinsessServiceUnitTest_BusinessService_SendBusinessDataGen()
35         {
36             Order order = new Order(Guid.NewGuid().ToString());
37 
38             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 10.0F });
39             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 15.0F });
40 
41             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 20.0F });
42             order.AddItem(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 2, Price = 30.0F });
43 
44             ISendServiceData mockISendServiceData = Substitute.For<ISendServiceData>();
45             object sendresult = null;
46             mockISendServiceData.When(isend => isend.Send(Arg.Any<object>())).Do(callinfo => sendresult = callinfo.Arg<object>());
47 
48             BusinessService testService = new BusinessService(mockISendServiceData);
49             testService.SendBusinessData<Order>(ord => { return ord.SumPrices(1); }, order);
50 
51             Assert.AreEqual((double)sendresult, 25);
52         }
53     }
54 }
View Code

在第二個單元測試方法裡面我們將使用Lambda方式將邏輯直接注入進BusinessService中,好就好這裡;可以將Lambda封進Expression<T>然後直接儲存在Cache中或者配置中間,徹底告別反射呼叫吧,就好比委託一樣沒有人會在使用委託在定義個沒用的方法;(所以函數語言程式設計越來越討人喜歡了,可以關注一下F#;)總之使用泛型解決型別不確定問題,使用Lambda解決程式碼邏輯注入;大膽的嘗試吧,將宣告與實現徹底分離;

(對.NET單元測試有興趣的朋友後面一篇文章會詳細的講解一下如何做單元測試,包括Mock框架的使用;)

3】被忽視的特性(Attribute)設計方式

大部分人對特性的定義是程式碼的“資料註釋”,就是可以在執行時讀取這個特性用來做型別的附加屬性用的;通常在一些框架中對DomainModel中的Entity進行邏輯上的關聯用的,比如我們比較熟悉的ORM,都會在Entity的上面加上一個類似 [Table(TableName=”Order”)] 這樣的特性宣告,然後再在自己的框架中通過反射的方式去在執行時差找後設資料找到這個特性,然後就可以對附加了這個特性的型別進行相關的處理;

這其實沒有問題,很正常的設計思路,也是比較通用的設計方法;但是我們的思維被前人固化了,難道特性就只能作為程式碼的宣告嗎?問過自己這個問題嗎?

我們繼續使用上面2】小結中的程式碼作為本節演示程式碼,現在我們假設需要在DomainModel中的Entity上面加上兩個特性第一個用來斷定它是否需要做Cache,第二個用來確定關於Entity操作驗證的特性;

看程式碼:

1 /// <summary>
2     /// Order.
3     /// </summary>
4     [EntityCache(10, true)]
5     [EntityValidator(ValidatorOperationType.All)]
6     public class Order
7     {}
View Code

程式碼應該很明瞭,第一EntityCache用來設計實體的快取,引數是快取的過期時間;第二個特性EntityValidator用來設定當實體進行相關處理的時候需要的驗證型別,這裡選擇是所有操作;

現在的問題是關於特性的優先順序,對於Order類的處理到底是先Cache然後驗證,還是先驗證然後Cache或者說內部沒有進行任何的邏輯處理;如果我們將特性的視為程式碼的標識而不是真正的邏輯,那麼對於優先順序的處理會比較棘手,你需要設計如何將不同的特性處理邏輯關聯起來;比較合理的設計方法是特性的處理連結串列;本人之前設計過AOP的簡單框架,就遇到過對於特性的優先順序的處理經驗,也是用的連結串列的方式將所有的特性按照順序串聯起來然後將物件穿過特性內部邏輯,這也符合DDD的中心思想;

下面我們來看程式碼:

 1 Codeusing System;
 2 
 3 namespace Infrastructure.Common
 4 {
 5     [AttributeUsage(AttributeTargets.Class)]
 6     public abstract class EntityOperation : Attribute
 7     {
 8         protected EntityOperation NextOperation { get; set; }
 9     }
10 }
View Code

我們抽象出所有的處理,然後在內部包含下一個處理邏輯的特性例項;然後讓各自的Attribute繼承自它;

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace Infrastructure.Common.Cache
 8 {
 9     [AttributeUsage(AttributeTargets.Class)]
10     public class EntityCache : EntityOperation
11     {
12         public EntityCache(int cacheTime, bool IsEnable)
13         {
14             this.ExpireTime = cacheTime;
15         }
16 
17         public int ExpireTime { get; set; }
18 
19     }
20 }
View Code
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace Infrastructure.Common.Validator
 8 {
 9     public enum ValidatorOperationType
10     {
11         Insert,
12         Delete,
13         Update,
14         Select,
15         All
16     }
17 
18     [AttributeUsage(AttributeTargets.Class)]
19     public class EntityValidator : EntityOperation
20     {
21         public EntityValidator(ValidatorOperationType validatorType)
22         {
23 
24         }
25     }
26 }
View Code

根據特性在類的先後順序就可以控制他們的優先順序;

圖2:

上圖很直觀的表現了連結串列設計思想,再通過仔細的加工應該會很不錯的;

4】擴充套件方法讓你的物件如虎添翼(要學會使用擴充套件方法的設計思想)

擴充套件方法我們用的應該不算少的了,在一些新的框架中到處都能看見擴充套件方法的優勢,比如:ASP.NETMVC、EntityFramework等等特別是開源的框架用的很多;

那麼我們是不是還停留在原始社會,應該嘗試接受新的設計思想,儘管一開始可能不太適應,但是當你適應了之後會讓你的設計思想提升一個境界;

下面我們還是使用上面的演示程式碼來進行本節的程式碼演示,現在假如有一個這樣的需求,為了保證DomainModel的完全乾淨,我們在應用層需要對領域模型加入一些非業務性的行為,這些行為跟DomainModel本身沒有直接關係,換句話說我們這裡的Order聚合實體可能需要一個獲取Order在Cache中存活了多長時間的方法;那麼在以往我們可能提供一個方法然後把Order例項作為引數這樣來使用,但是這裡我們的需求是該方法是Order物件的方法而不是其他地方的方法;

所以這裡使用擴充套件方法就可以在不改變物件本身業務邏輯的情況下擴充套件物件行為;最關鍵的是擴充套件方法為後面的鏈式程式設計提供了基石;從長遠來看DomainModel將會被獨立到ThreadProcess中,當系統初始化時部分的DomainModel將直接駐留在記憶體中,然後通過系統本地化將擴充套件方法加入,這樣就可以在不改變物件的情況下新增行為,這也為行為驅動設計提供了好的技術實現;

用純技術性的假設沒有說服力,上面說給領域本身加上獲取Cache的方法,肯定會有朋友說這完全沒有必要,提供一個簡單的方法就OK了,恩 我覺得也有道理,那麼下面的需求你將不得不說妙;

【需求簡述】:物件本身的行為不是固定不變的,尤其我們現在設計物件的時候會將物件在全域性情況下的所有行為都定義在物件內部,比如我們正常人,在不同的角色中才具有不同的行為,我們只有在公司才具有“開啟伺服器”的行為,只有在家裡才可以“親吻”自己的老婆的行為;難道我們在設計User類的時候都將這些定義在物件內部嗎?顯然不符合邏輯,更不符合物件導向設計思想,當然我們目前基本上都是這麼做的;

(有興趣的朋友可以參考:BDD(行為驅動設計)、DCI(資料、上下文、互動)設計思想;)

現在我們來為Order新增一組行為,但是這組 行為只有在某些場景下才能使用,這裡只是為了演示而用,真要在專案中設計還需要考慮很多其他因素;

 1 namespace DomainModelBehavior.Order.ShippingBehavior
 2 {
 3     using ConsoleApplication1.DomainModel;
 4     public static class OrderBehavior
 5     {
 6         public static Order TaxRate(this Order order)
 7         {
 8             return order;
 9         }
10     }
11 }
12 
13 namespace DomainModelBehavior.Order.ShoppingCart
14 {
15     using ConsoleApplication1.DomainModel;
16     public static class OrderBehavior
17     {
18         public static Order Inventory(this Order order)
19         {
20             return order;
21         }
22     }
23 }
View Code

這裡有兩個位於不同namespace中的行為,他們對應不同的場景;第一個TaxRate用來計算稅率的行為,只有在Order物件已經處於提交狀態時用的;那麼第二個行為Inventory用來計算庫存的,使用者在Shoppingcart的時候用來確定是否有足夠的庫存;當然這裡我只是假設;

然後我們就可以在不同的場景下進行名稱空間的引用,比如我們現在Shoppingcart階段將不會使用到TaxRate行為;

 1 using System;
 2 
 3 namespace ConsoleApplication1
 4 {
 5     using DomainModelBehavior.Order.ShoppingCart;
 6     class Program
 7     {
 8         static void Main(string[] args)
 9         {
10             DomainModel.Order order = new DomainModel.Order(Guid.NewGuid().ToString());
11             order.Inventory();
12         }
13     }
14 }
View Code

例子雖然有點簡單,但是應該能說明擴充套件方法的基本使用方式,對於DCI架構的實現會複雜很多,需要好好設計才行;

5】別怕Static屬性(很多人都怕Static在Service模式下的設計,其實要學會使用執行緒本地儲存(ThreadStatic))

很多時候我們在設計物件的時候,尤其是面向Context型別的,很希望能通過某個靜態屬性直接能拿到Context,所以會定義一個靜態屬性用來儲存物件的某個例項;但是會有很多人都會排斥靜態屬性,動不動就說效能問題,動不動就收多執行緒不安全等等藉口,難道靜態屬性就沒有存在必要了嘛;

不用靜態屬性你哪來的ASP.NET中的CurrentContext直接,如果怕因為多執行緒問題導致資料不完整,建議使用執行緒本地儲存;沒有什麼好怕的,多用就熟悉了;用也很簡單,直接在靜態屬性上面加上這個特性就OK了,前提是你已經考慮了這個屬性是執行緒內部共享的不是應用程式級別的共享;

1 /// <summary>
2         /// 資料來源的操作
3         /// </summary>
4         [ThreadStatic]
5         private static IDataSourceOperation datasourceoperation = IDataSourceOperationFactory.Create();
View Code

6】泛型的協變與逆變(設計架構介面(Interface)時要注意物件的協變、逆變)

越來越多的人喜歡自己搗鼓點東西出來用用,這很不錯,時間長了設計能力自然會得到提升的;但是最近發現我們很多泛型在設計上缺乏轉換的控制,也就是這裡的協變和逆變;我們有一個Item型別,現在我們需要對它進行更加具體化,我們派生出一個Apple型別的Item;

1 List<Apple> apples = new List<Apple>();
2  List<Item> items = apples;
View Code

 這段程式碼是編譯不通過的,因為List<T> 在定義的時候就不支援逆變、但是如果換成下面這樣的程式碼是完全可以的;

1 List<Apple> apples = new List<Apple>();
2  IEnumerable<Item> items = apples;
View Code

很容易的就可以得到集合的轉換,雖然很簡單的功能但是在設計上如果運用好的話能大大改變介面的靈活性;你可能會有一個疑問,為什麼具體實現List<T>不支援協變而IEnumerable<out T>反而支援協變;這就是物件導向設計的思想,介面本質是抽象的,抽象的不會有具體的實現所以它作為協變不會存在問題,但是逆變就會有問題;

7】使用泛型的型別推斷(還在為引數型別煩惱嗎)

在設計泛型方法的時候要學會使用型別推斷技巧,這樣會很方便的在呼叫的時候減少你顯示呼叫<>的程式碼,也會顯得很優美;大家應該都比較熟悉Func泛型委託,它是C#3中的主角,也是函數語言程式設計的基礎,我們在設計某個方法的時候會將邏輯暴露在外部,然後通過Lambda的方式注入進來;現在的LINQ都是這麼實現的,比較熟悉的Where方法、Select方法,都需要我們提供一個作為它內部邏輯的函式段;

1 List<Item> items = new List<Item>();
2             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
3             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
4             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
5             items.Add(new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
6 
7             items.Where<Item>(item => item.ItemUsingType == 1);
8             items.Where(item=>item.ItemUsingType==1);
View Code

這裡有兩種呼叫Where的程式碼,哪一種看上去舒服一點有沒一點,不用我說了;那我們看一下它的定義:

1 public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
View Code

我們看到TSource型別佔位符,很容易理解,這是一個擴充套件IEnumerable<TSource>型別的方法,系統會自動的匹配TSource;我們在設計的時候也要借鑑這種好的設計思想;

(有興趣的朋友可以參見本人的:.NET深入解析LINQ框架(一:LINQ優雅的前奏)

8】鏈式程式設計(設計符合大腦思維習慣的處理流程)

其實那麼多的C#新特性都是為了能讓我們編寫程式碼能更方便,總之一句話是為了更符合大腦思維習慣的程式設計模式;

C#從純物件導向漸漸的加入了函式式模式,從靜態型別逐漸加人動態型別特性;C#現在變成多正規化程式語言,其實已經很大程度滿足我們的日常需求;以往我們都會為了動態行為編寫複雜的Emit程式碼,用很多CodeDom的技術;現在可以使用Dymanic解決了;

這節我們來看一下關於如何設計線性的鏈式方法,這不是技術問題,這是對需求的理解能力;可以將鏈式思想用在很多地方,只要有邏輯有流程的地方都可以進行相關設計,首先你要保證你是一個正常思考問題的人,別設計出來的方法是反的,那麼用的人會很不爽的;這裡我舉一個我最近遇到的問題;

8.1】鏈式程式設計(多條件(方法碎片化)呼叫

我們都熟悉DTO物件,它是從UI傳過來的資料集合,簡單的業務邏輯Application Layer將它轉換成DomainModel中的Entity,如果複雜的業務邏輯是不能直接將DTO進行轉換的;但是在轉換過程中我們總是少不了對它的屬性判斷,如果UserName不為空並且Password不為空我才能去驗證它的合法性,等等;類似這樣的判斷;這裡我們將執行擴充套件方法將這些邏輯判斷鏈起來,並且最後輸出一個完整的Entity物件;

 1 using System.Collections.Generic;
 2 
 3 namespace ConsoleApplication1
 4 {
 5     public static class DtoValidator
 6     {
 7         public static IEnumerable<TSource> IsNull<TSource>(this IEnumerable<TSource> tList)
 8         {
 9             return tList;
10         }
11         public static IEnumerable<TSource> IsLength<TSource>(this IEnumerable<TSource> tList, int max)
12         {
13             return tList;
14         }
15         public static IEnumerable<TResult> End<TResult>(this IEnumerable<object> tList, IEnumerable<TResult> result)
16         {
17             result = new List<TResult>();
18             return result;
19         }
20     }
21 }
View Code

有一組擴充套件方法,用來做驗證用的;

1 List<Order> orderList = null;
2 
3 List<Dto.OrderInfoDto> dtoList = new List<Dto.OrderInfoDto>();
4 
5 dtoList.IsNull().IsLength(3).End(orderList);
View Code

由於時間關係我這裡只是演示一下,完全可以做的很好的,在判斷的最後拿到返回的列表引用最後把資料送出來;

(有一個開源驗證框架應該還不錯,目前工作中在用:FluentValidator)

9】部分類、部分方法的使用(擴大設計範圍)

部分類不是新的特性,而部分方法是新特性;我們通過靈活運用部分類可以將發揮很大作用,比如我們完全可以將類的部分實現完全隔離在外部,起到低耦合的作用,甚至可以將宣告式設計超程式設計運用在C#中,比較經典就是ASP.NET後臺程式碼和前臺的模板程式碼,在執行時然後再通過動態編譯合起來,我們不要忘記可以使用部分類、部分方法來達到在執行時連結編譯時程式碼和執行時程式碼,類似動態呼叫的效果;由於這部分內容比較簡單,是設計思想的東西,所以沒有什麼要演示的,只是一個總結;

 

總結:內容雖然簡單,但是要想運用的好不簡單,這裡我只是總結一下,希望對大家有用,謝謝;

示例DEMO地址http://files.cnblogs.com/wangiqngpei557/ConsoleApplication1.zip

相關文章