【轉載】 .NET框架設計—常被忽視的C#設計技巧

閱讀目錄:html

  • 1.開篇介紹編程

  • 2.儘可能使用Lambda匿名函數調用代替反射調用(走進聲明式設計)c#

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

 

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

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

上面代碼應該沒有問題,基本的訂單領域模型你們都太熟了;爲了保證上面的代碼是絕對的正確,以避免程序錯誤形成閱讀者的不爽,因此都會有100%的單元測試覆蓋率;這裏咱們主要使用的是Order類中的SumPrices方法,因此它的UnitTest是100%覆蓋;

圖1:

Order中的SumPrices方法的UnitTest代碼:

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

 

在以往我基本上不寫單元測試的,可是最近工做上基本上都須要寫每一個方法的單元測試,並且要求是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
2
3
4
5
6
7
8
9
using System;
namespace Infrastructure.Common
{
     public interface IBusinessService
     {
         void SendBusinessData( object callAuthor, string methodName, object parameterInstance);
         void SendBusinessData<P>(Func<P, object > callFun, P parameter);
     }
}
 

這是業務調用接口;

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

 

這裏簡單實現IBusinessService接口,其實代碼很簡單,第一個方法使用反射的方式調用代碼,而第二個方法則使用委託調用;在實現類裏面還包含了一個簡單的接口;

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

目的是爲了方便單元測試,咱們來看一下單元測試代碼;

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

在第二個單元測試方法裏面咱們將使用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
2
3
4
5
6
7
/// <summary>
     /// Order.
     /// </summary>
     [EntityCache(10, true )]
     [EntityValidator(ValidatorOperationType.All)]
     public class Order
     {}
 

代碼應該很明瞭,第一EntityCache用來設計實體的緩存,參數是緩存的過時時間;第二個特性EntityValidator用來設置當實體進行相關處理的時候須要的驗證類型,這裏選擇是全部操做;

如今的問題是關於特性的優先級,對於Order類的處理究竟是先Cache而後驗證,仍是先驗證而後Cache或者說內部沒有進行任何的邏輯處理;若是咱們將特性的視爲代碼的標識而不是真正的邏輯,那麼對於優先級的處理會比較棘手,你須要設計如何將不一樣的特性處理邏輯關聯起來;比較合理的設計方法是特性的處理鏈表;本人以前設計過AOP的簡單框架,就遇到過對於特性的優先級的處理經驗,也是用的鏈表的方式將全部的特性按照順序串聯起來而後將對象穿過特性內部邏輯,這也符合DDD的中心思想;

下面咱們來看代碼:

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

 

咱們抽象出全部的處理,而後在內部包含下一個處理邏輯的特性實例;而後讓各自的Attribute繼承自它;

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

 

根據特性在類的前後順序就能夠控制他們的優先級;

圖2:

2

上圖很直觀的表現了鏈表設計思想,再經過仔細的加工應該會很不錯的;

4】擴展方法讓你的對象如虎添翼(要學會使用擴展方法的設計思想)

擴展方法咱們用的應該不算少的了,在一些新的框架中處處都能看見擴展方法的優點,好比:ASP.NETMVC、EntityFramework等等特別是開源的框架用的不少;

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

 

下面咱們仍是使用上面的演示代碼來進行本節的代碼演示,如今假若有一個這樣的需求,爲了保證DomainModel的徹底乾淨,咱們在應用層須要對領域模型加入一些非業務性的行爲,這些行爲跟DomainModel自己沒有直接關係,換句話說咱們這裏的Order聚合實體可能須要一個獲取Order在Cache中存活了多長時間的方法;那麼在以往咱們可能提供一個方法而後把Order實例做爲參數這樣來使用,可是這裏咱們的需求是該方法是Order對象的方法而不是其餘地方的方法;

因此這裏使用擴展方法就能夠在不改變對象自己業務邏輯的狀況下擴展對象行爲;最關鍵的是擴展方法爲後面的鏈式編程提供了基石;從長遠來看DomainModel將會被獨立到ThreadProcess總,當系統初始化時部分的DomainModel將直接主流在內存中,而後經過系統本地化將擴展方法加入,這樣就能夠在不改變對象的狀況下添加行爲,這也爲行爲驅動設計提供了好的技術實現;

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

【需求簡述】:對象自己的行爲不是固定不變的,尤爲咱們如今設計對象的時候會將對象在全局狀況下的全部行爲都定義在對象內部,好比咱們正常人,在不一樣的角色中才具備不一樣的行爲,咱們只有在公司才具備「打開服務器」的行爲,只有在家裏才能夠「親吻」本身的老婆的行爲;難道咱們在設計User類的時候都將這些定義在對象內部嗎?顯然不符合邏輯,更不符合面向對象設計思想,固然咱們目前基本上都是這麼作的;

(有興趣的朋友能夠參考:BDD(行爲驅動設計)、DCI(數據、上下文、交互)設計思想;)

如今咱們來爲Order添加一組行爲,可是這組 行爲只有在某些場景下才能使用,這裏只是爲了演示而用,真要在項目中設計還須要考慮不少其餘因素;

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

 

這裏有兩個位於不一樣namespace中的行爲,他們對應不一樣的場景;第一個TaxRate用來計算稅率的行爲,只有在Order對象已經處於提交狀態時用的;那麼第二個行爲Inventory用來計算庫存的,用戶在Shoppingcart的時候用來肯定是否有足夠的庫存;固然這裏我只是假設;

而後咱們就能夠在不一樣的場景下進行命名空間的引用,好比咱們如今Shoppingcart階段將不會使用到TaxRate行爲;

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

例子雖然有點簡單,可是應該能說明擴展方法的基本使用方式,對於DCI架構的實現會複雜不少,須要好好設計才行;

 

5】別怕Static屬性(不少人都怕Static在Service模式下的設計,其實要學會使用線程本地存儲(ThreadStatic))

不少時候咱們在設計對象的時候,尤爲是面向Context類型的,很但願能經過某個靜態屬性直接能拿到Context,因此會定義一個靜態屬性用來保存對象的某個實例;可是會有不少人都會排斥靜態屬性,動不動就說性能問題,動不動就收多線程不安全等等藉口,難道靜態屬性就沒有存在必要了嘛;

不用靜態屬性你哪來的ASP.NET中的CurrentContext直接,若是怕由於多線程問題致使數據不完整,建議使用線程本地存儲;沒有什麼好怕的,多用就熟悉了;用也很簡單,直接在靜態屬性上面加上這個特性就OK了,前提是你已經考慮了這個屬性是線程內部共享的不是應用程序級別的共享;

 

1
2
3
4
5
/// <summary>
/// 數據源的操做
/// </summary>
[ThreadStatic]
private static IDataSourceOperation datasourceoperation = IDataSourceOperationFactory.Create();

6】泛型的協變與逆變(設計架構接口(Interface)時要注意對象的協變、逆變)

愈來愈多的人喜歡本身搗鼓點東西出來用用,這很不錯,時間長了設計能力天然會獲得提高的;可是最近發現咱們不少泛型在設計上缺少轉換的控制,也就是這裏的協變和逆變;咱們有一個Item類型,如今咱們須要對它進行更加具體化,咱們派生出一個Apple類型的Item;

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

這段代碼是編譯不經過的,由於List<T> 在定義的時候就不支持逆變、可是若是換成下面這樣的代碼是徹底能夠的;

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

很容易的就能夠獲得集合的轉換,雖然很簡單的功能可是在設計上若是運用好的話能大大改變接口的靈活性;你可能會有一個疑問,爲何具體實現List<T>不支持協變而IEnumerable<out T>反而支持協變;這就是面向對象設計的思想,接口本質是抽象的,抽象的不會有具體的實現因此它做爲協變不會存在問題,可是逆變就會有問題;

 

7】使用泛型的類型推斷(還在爲參數類型煩惱嗎)

在設計泛型方法的時候要學會使用類型推斷技巧,這樣會很方便的在調用的時候減小你顯示調用<>的代碼,也會顯得很優美;你們應該都比較熟悉Func泛型委託,它是C#3中的主角,也是函數式編程的基礎,咱們在設計某個方法的時候會將邏輯暴露在外部,而後經過Lambda的方式注入進來;如今的LINQ都是這麼實現的,比較熟悉的Where方法、Select方法,都須要咱們提供一個做爲它內部邏輯的函數段;

1
2
3
4
5
6
7
List<Item> items = new List<Item>();
             items.Add( new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
             items.Add( new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
             items.Add( new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
             items.Add( new Item() { ItemCode = Guid.NewGuid(), ItemUsingType = 1, Price = 20 });
             items.Where<Item>(item => item.ItemUsingType == 1);
             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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Collections.Generic;
namespace ConsoleApplication1
{
     public static class DtoValidator
     {
         public static IEnumerable<TSource> IsNull<TSource>( this IEnumerable<TSource> tList)
         {
             return tList;
         }
         public static IEnumerable<TSource> IsLength<TSource>( this IEnumerable<TSource> tList, int max)
         {
             return tList;
         }
         public static IEnumerable<TResult> End<TResult>( this IEnumerable< object > tList, IEnumerable<TResult> result)
         {
             result = new List<TResult>();
             return result;
         }
     }
}
 

有一組擴展方法,用來作驗證用的;

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

因爲時間關係我這裏只是演示一下,徹底能夠作的很好的,在判斷的最後拿到返回的列表引用最後把數據送出來;

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

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

部分類不是新的特性,而部分方法是新特性;咱們經過靈活運用部分類能夠將發揮很大做用,好比咱們徹底能夠將類的部分實現徹底隔離在外部,起到低耦合的做用,甚至能夠將聲明式設計元編程運用在C#中,比較經典就是ASP.NET後臺代碼和前臺的模板代碼,在運行時而後再經過動態編譯合起來,咱們不要忘記可使用部分類、部分方法來達到在運行時連接編譯時代碼和運行時代碼,相似動態調用的效果;

因爲這部份內容比較簡單,是設計思想的東西,因此沒有什麼要演示的,只是一個總結;

 

 

總結:內容雖然簡單,可是要想運用的好不簡單,這裏我只是總結一下,但願對你們有用,謝謝;

 

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

 

做者:王清培

出處:http://wangqingpei557.blog.51cto.com/

http://wangqingpei557.blog.51cto.com/1009349/1259767

相關文章
相關標籤/搜索