閱讀目錄:
- 1.開篇介紹
- 2.盡量使用Lambda匿名函數調用代替反射調用(走進聲明式設計)
- 3.被忽視的特性(Attribute)設計方式
- 4.擴展方法讓你的對象如虎添翼(要學會使用擴展方法的設計思想)
- 5.別怕Static屬性(很多人都怕Static在Service模式下的設計,其實要學會使用線程本地存儲(ThreadStatic))
- 6.泛型的協變與逆變(設計架構接口(Interface)時要時刻注意對象的協變、逆變)
- 7.使用泛型的類型推斷(還在為參數類型煩惱嗎)
- 8.鏈式編程(設計符合大腦思維習慣的處理流程)
- 8.1.鏈式編程(多條件(方法碎片化)調用
- 9.部分類、部分方法的使用(擴大設計范圍)
1.】開篇介紹
本文中的內容都是我無意中發現覺得有必要分享一下的設計經驗,沒有什么高深的技術,只是平時我們可能會忽視的一些設計技巧;為什么有這種想法是因為之前跟一些同事交流技術的時候會發現很多設計思維被固化了,比如之前我在做客戶端框架開發的時候會去設計一些關於Validator、DTO Transfer等常用的Common function,但是發現在討論某一些技術實現的時候會被弄的雲里霧里的,會自我郁悶半天,不會及時的明白對方在說的問題;
后來發現他們一是沒有把概念分清楚,比如.NETFramework、C#、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 }
這是一個訂單領域實體,它里面引用了一個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 }
上面代碼應該沒有問題,基本的訂單領域模型大家都太熟了;為了保證上面的代碼是絕對的正確,以免程序錯誤造成閱讀者的不爽,所以都會有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 }
在以往我基本上不寫單元測試的,但是最近工作上基本上都需要寫每個方法的單元測試,而且要求是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 }
這是業務調用接口;

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 }
這里簡單實現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 }
目的是為了方便單元測試,我們來看一下單元測試代碼;

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 }
在第二個單元測試方法里面我們將使用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 {}
代碼應該很明了,第一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 }
我們抽象出所有的處理,然后在內部包含下一個處理邏輯的特性實例;然后讓各自的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 }

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 }
根據特性在類的先后順序就可以控制他們的優先級;
圖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 }
這里有兩個位於不同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 }
例子雖然有點簡單,但是應該能說明擴展方法的基本使用方式,對於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();
6】泛型的協變與逆變(設計架構接口(Interface)時要注意對象的協變、逆變)
越來越多的人喜歡自己搗鼓點東西出來用用,這很不錯,時間長了設計能力自然會得到提升的;但是最近發現我們很多泛型在設計上缺乏轉換的控制,也就是這里的協變和逆變;我們有一個Item類型,現在我們需要對它進行更加具體化,我們派生出一個Apple類型的Item;

1 List<Apple> apples = new List<Apple>(); 2 List<Item> items = apples;
這段代碼是編譯不通過的,因為List<T> 在定義的時候就不支持逆變、但是如果換成下面這樣的代碼是完全可以的;

1 List<Apple> apples = new List<Apple>(); 2 IEnumerable<Item> items = apples;
很容易的就可以得到集合的轉換,雖然很簡單的功能但是在設計上如果運用好的話能大大改變接口的靈活性;你可能會有一個疑問,為什么具體實現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);
這里有兩種調用Where的代碼,哪一種看上去舒服一點有沒一點,不用我說了;那我們看一下它的定義:

1 public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
我們看到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 }
有一組擴展方法,用來做驗證用的;

1 List<Order> orderList = null; 2 3 List<Dto.OrderInfoDto> dtoList = new List<Dto.OrderInfoDto>(); 4 5 dtoList.IsNull().IsLength(3).End(orderList);
由於時間關系我這里只是演示一下,完全可以做的很好的,在判斷的最后拿到返回的列表引用最后把數據送出來;
(有一個開源驗證框架應該還不錯,目前工作中在用:FluentValidator)
9】部分類、部分方法的使用(擴大設計范圍)
部分類不是新的特性,而部分方法是新特性;我們通過靈活運用部分類可以將發揮很大作用,比如我們完全可以將類的部分實現完全隔離在外部,起到低耦合的作用,甚至可以將聲明式設計和元編程運用在C#中,比較經典就是ASP.NET后台代碼和前台的模板代碼,在運行時然后再通過動態編譯合起來,我們不要忘記可以使用部分類、部分方法來達到在運行時鏈接編譯時代碼和運行時代碼,類似動態調用的效果;由於這部分內容比較簡單,是設計思想的東西,所以沒有什么要演示的,只是一個總結;
總結:內容雖然簡單,但是要想運用的好不簡單,這里我只是總結一下,希望對大家有用,謝謝;
示例DEMO地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication1.zip
作者:王清培
出處:http://www.cnblogs.com/wangiqngpei557/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。