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


閱讀目錄:

  • 1.開篇介紹
  • 2.元數據緩存池模式(在運行時構造元數據緩存池)
    • 2.1.元數據設計模式(抽象出對數據的描述數據)
    • 2.2.借助Dynamic來改變IOC、AOP動態綁定的問題
    • 2.3.元數據和模型綁定、元數據應該隱藏在Model背后、元數據與DSL的關系
  • 3.鏈式配置Dynamic模式(愛不釋手的思維習慣編程)
  • 4.委托工廠模式(要優於常見的 工廠,概念更加准確,減少污染)
  • 5.規則外掛(視委托為特殊的規則對象原型)

1】開篇介紹

通過上一篇的“.NET框架設計—常被忽視的C#設計技巧”一文來看,對於框架設計的技巧還是有很多人比較有興趣的,那么框架設計思想對於我們日常開發來說其實並不是很重要,但是對於我們理解框架背后的運行原理至關重要;當我們使用着LINQ靈活的語法的同時我們是否能理解它的背后運行原理、設計原理更深一點就是它的設計模式及復雜的對象模型;

從一開始學習.NET我就比較喜歡框架背后的設計模型,框架提供給我們的使用接口是及其簡單的,單純從使用上來看我們不會隨着對框架的使用時間而增加我們對框架內部設計的理解,反而會養成一樣拿來即用的習慣,我們只有去了解、深挖它的內部設計原理才是我們長久學習的目標;因為框架的內部設計模式是可以提煉出來並被總結的;

 

這篇文章總結了幾個我最近接觸的框架設計思想,可以稱他們為模式;由於時間關系,這里只是介紹加一個簡單的介紹和示例讓我們能基本的了解它並且能在日后設計框架的時候想起來有這么一個模式、設計方式可以借鑒;當然,這每一節都是一個很大主題,用的時候在去細心的分析學習吧;

2】元數據緩存池模式(在運行時構造元數據緩存池)

很多框架都有將特性放在屬性上面用來標識某種東西,但是這種方式使用不當的話會對性能造成影響;再從框架設計原則來講也是對DomainModel極大的污染,從EntityFramework5.0之前的版本我們就能體會到了,它原本是將部分Attribute加在了Entity上的,但是這畢竟是業務代碼的核心,原則上來說這不能有任何的污染,要絕對的POJO;后來5.0之后就完全獨立了DomainModel.Entity,所有的管理都在運行時構造綁定關系,因為它有EDMX元數據描述文件;

那么這些Attribute其實本質是.NET在運行時的一種元數據,主要的目的是我們想在運行時將它讀取出來,用來對某些方面的判斷;那么現在的問題是如果我們每次都去讀取這個Attribute是必須要走反射機制,當然你可以找一些框架來解決這個問題;(我們這里討論的是你作為開發框架的設計者!)

反射影響性能這不用多講了,那么常規的做法是會在第一次反射之后將這些對象緩存起來,下次再用的時候直接在緩存中讀取;這沒有問題,這是解決了反射的性能問題,那么你的Attribute是否還要加在DomainModel中呢,如果加的話隨着代碼量的增加,這些都會成為后面維護的成本開銷;那么我們如何將干凈的POJO對象提供給程序員用,但是在后台我們也能對POJO進行強大的控制?這是否是一種設計問題?

2.1】元數據設計模式(抽象出對數據的描述數據)

我一直比較關注對象與數據之間的關系,面向對象的這種縱橫向關系如何平滑的與E-R實體關系模型對接,這一直是復雜軟件開發的核心問題;這里就用它來作為本章的示例的基本概要;

我們有一個基本的DomainModel聚合,如何在不影響本身簡潔性的情況下與E-R關系對接,比如我們在對聚合進行一個Add操作如何被映射成對數據庫的Insert操作;我們來看一下元數據設計模式思想;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Employee.<see cref="DomainModel.Employee"/>
12     /// </summary>
13     public class Employee
14     {
15         /// <summary>
16         /// Primary id.
17         /// </summary>
18         public string EId { get; set; }
19 
20         /// <summary>
21         /// Name.
22         /// </summary>
23         public string Name { get; set; }
24 
25         /// <summary>
26         /// Sex.<see cref="DomainModel.SexType"/>
27         /// </summary>
28         public SexType Sex { get; set; }
29 
30         /// <summary>
31         /// Address.
32         /// </summary>
33         public Address Address { get; set; }
34     }
35 }
View Code

這里有一個以Employee實體為聚合根的聚合,里面包含一些基本的屬性,特別需要強調的是Sex屬性和Address,這兩個屬性分別是Complex類型的屬性;
Complex類型的屬性是符合面向對象的需要的,但是在關系型數據庫中是很難實現的,這里就需要我們用元數據將它描述出來並能在一些行為上進行控制;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Address .
12     /// </summary>
13     public struct Address
14     {
15         /// <summary>
16         /// Address name.
17         /// </summary>
18         public string AddressName { get; set; }
19     }
20 }
View Code

這是Address類型的定義;

 1 namespace ConsoleApplication1.DomainModel
 2 {
 3     /// <summary>
 4     /// Sex type.
 5     /// </summary>
 6     public enum SexType
 7     {
 8         Male,
 9         Female
10     }
11 }
View Code

這是SexType類型的定義;都比較簡單;  

只有這樣我們才能對DomainModel進行大面積的復雜設計,如果我們不能將數據對象化我們無法使用設計模式,也就談不上擴展性;

圖1:

這是我們的對象模型,那么我們如何將它與數據庫相關的信息提取出來形成獨立的元數據信息,對元數據的抽取需要動、靜結合才行;

什么動、靜結合,我們是否都會碰見過這樣的問題,很多時候我們的代碼在編譯時是確定的,但是有部分的代碼需要在運行時動態的構造,甚至有些時候代碼需要根據當前的IDE來生成才行,但是最終在使用的時候這些在不同階段生成的代碼都需要結合起來變成一個完整的元數據對象;

框架在很多時候需要跟IDE結合才能使使用變的順手,比如我們在開發自己的ORM框架如果不能直接嵌入到VisualStudio中的話,用起來會很不爽;當我們用自己的插件去連接數據庫並且生成代碼的時候,有部分的元數據模型已經在代碼中實現,但是有部分需要我們動態的去設置才行;

我們來看一下關於元數據的基礎代碼;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ORM.Meta
 9 {
10     using System;
11     using System.Collections.Generic;
12     using System.Linq;
13     using System.Text;
14     using System.Threading.Tasks;
15 
16     /// <summary>
17     /// Data source context.
18     /// </summary>
19     public abstract class DataBaseContext : List<MetaTable>, IDisposable
20     {
21         /// <summary>
22         /// Data base name.
23         /// </summary>
24         protected string DataBaseName { get; set; }
25 
26         /// <summary>
27         /// Connection string.
28         /// </summary>
29         protected string ConnectionString { get; set; }
30 
31         /// <summary>
32         /// Provider child class add table.
33         /// </summary>
34         /// <param name="table"></param>
35         protected virtual void AddTable(MetaTable table)
36         {
37             this.Add(table);
38         }
39 
40         /// <summary>
41         /// Init context.
42         /// </summary>
43         protected virtual void InitContext() { }
44         public void Dispose() { }
45     }
46 }
View Code

這表示數據源上下文,屬於運行時元數據的基礎設施;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ORM.Meta
 9 {
10     using System.Collections.Generic;
11     using System.Linq;
12 
13     /// <summary>
14     /// Database Table meta.
15     /// </summary>
16     public class MetaTable : List<MetaColumn>
17     {
18         /// <summary>
19         /// Table name.
20         /// </summary>
21         public string Name { get; set; }
22 
23         /// <summary>
24         /// Entity name.
25         /// </summary>
26         public string EntityName { get; set; }
27 
28         /// <summary>
29         /// Get column by column name.
30         /// </summary>
31         /// <param name="name">Column name.</param>
32         /// <returns><see cref="ORM.MetaColumn"/></returns>
33         public MetaColumn GetColumnByName(string name)
34         {
35             var column = from item in this.ToList() where item.CoumnName == name select item;
36             return column.FirstOrDefault();
37         }
38     }
39 }
View Code

簡單的表示一個Table,里面包含一系列的Columns;要記住在設計元數據基礎代碼的時候將接口留出來,方便在IDE中植入初始化元數據代碼;

圖2:

到目前為止我們都是在為元數據做基礎工作,我們看一下有系統生成的聲明的元數據代碼;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.Repository
 9 {
10     using System;
11     using System.Collections.Generic;
12     using System.Linq;
13     using System.Text;
14     using System.Threading.Tasks;
15 
16     /// <summary>
17     /// IDE Builder.
18     /// </summary>
19     public class DesignBuilder_DataBaseContext : ORM.Meta.DataBaseContext
20     {
21         //this begin IDE builder.
22         protected override void InitContext()
23         {
24             ORM.Meta.MetaTable metaTable = new ORM.Meta.MetaTable() { Name = "TB_Employee", EntityName = "Employee" };
25             metaTable.Add(new ORM.Meta.MetaColumn()
26             {
27                 CoumnName = "EId",
28                 DataType = ORM.Meta.DataType.NVarchar
29             });
30             metaTable.Add(new ORM.Meta.MetaColumn()
31             {
32                 CoumnName = "Name",
33                 DataType = ORM.Meta.DataType.NVarchar
34             });
35             metaTable.Add(new ORM.Meta.MetaColumn()
36             {
37                 CoumnName = "Sex",
38                 DataType = ORM.Meta.DataType.Int
39             });
40             metaTable.Add(new ORM.Meta.MetaColumn()
41             {
42                 CoumnName = "Address",
43                 DataType = ORM.Meta.DataType.NVarchar
44             });
45             this.AddTable(metaTable);
46         }
47         //end
48     }
49 }
View Code

我假設這是我們框架在IDE中生成的部分元數據代碼,當然你可以用任何方式來存放這些元數據,但是最后還是要去對象化;

圖3:

這個目錄你可以直接隱藏,在后台屬於你的框架需要的一部分,沒有必要讓它污染項目結構,當然放出來也有理由;如果想讓你的LINQ或者表達式能直接穿過你的元數據上下文你需要直接擴展;

1 static void Main(string[] args)
2         {
3             using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext())
4             {
5                 var employee = from emp in context.Employee where emp.EId == "Wqp123" select emp;
6             }
7         }
View Code

 

這里所有的代碼看上去很簡單,沒有多高深的技術,這也不是本篇文章的目的,任何代碼都需要設計的驅動才能產生價值,我們構建的基礎代碼都是元數據驅動;當你在運行時把這些元數據放入Cache,既不需要加Attribute也不需要反射反而活動了更大程度上的控制,但是要想構建一個能用的元數據結構需要結合具體的需求才行;

2.2】借助Dynamic來改變IOC、AOP動態綁定的問題

要想在運行時完全動態的綁定在編譯時定義的對象行為是需要強大的IOC框架支撐的,這樣的框架我們是做不來的或者需要很多精力,得不償失;對於元數據設計需要將AOP通過IOC的方式注入,在使用的時候需要改變一下思路,AOP的所有的切面在編譯時無法確定,后期通過IOC的方式將所有的行為注入;這里我們需要使用動態類型特性;

使用Dynamic之后我們很多以往不能解決問題都可以解決,更向元編程跨進了一步;對於IOC、AOP的使用也將變的很簡單,也有可能顛覆以往IOC、AOP的使用方式;而且動態編程將在很大程度上越過設計模式了,也就是設計模式的使用方式在動態編程中將不復存在了;

 1 using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext())
 2             {
 3                 var employees = from emp in context.Employee where emp.EId == "Wqp123" select emp;
 4 
 5                 Employee employee = new Employee() { EId = "Wqp123" };
 6 
 7                 var entityOpeartion = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee);
 8 
 9                 entityOpeartion.Add();
10             }
View Code

 

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DynamicBehavior
 9 {
10     using System;
11     using System.Dynamic;
12 
13     public class EntityDymanicBehavior
14     {
15         public static dynamic GetEntityBehavior<TEntity>(TEntity entity)
16         {
17             //auto mark entity behavior
18             dynamic dy = new ExpandoObject();
19             //load meta data mark dynamic behavior
20             dy.Entity = entity;
21             dy.Add = new Action(() =>
22             {
23                 Console.WriteLine("Action Add " + entity.GetType());
24             });
25             return dy;
26         }
27     }
28 }
View Code

圖4:

畫紅線的部分是可以抽取來放入擴展方法Add中的,在構造的內部是完全可以進入到元數據緩存池中拿到這些數據然后直接動態生成擴展方法背后的真實方法;

2.3】元數據和模型綁定、元數據應該隱藏在Model背后、元數據與DSL的關系

元數據的綁定應該在運行時動態去完成,這點在以往我們需要大費力氣,通過CodeDom、Emit才能完成,但是現在可以通過Dynamic、DLR來完成;思維需要轉變一下,動態編程我們以往用的最多的地方在JS上,現在可以在C#中使用,當然你也可以使用專門的動態語言來寫更強大的元數據框架,IronRuby、IronPython都是很不錯的,簡單的了解過Ruby的元數據編程,很強大,如果我們.NET程序員眼饞就用Iron…系列;

在開發復雜的動態行為時盡量使用元數據設計思想,不要把數據和表示數據的數據揉在一起,要把他們分開,在運行時Dynamic綁定;元數據應該在Model的背后應該在DomainModel的背后;

元數據和DSL有着天然的淵源,如果我們能把所有的語句組件化就可以將其封入.NET組件中,在IDE中進行所見即所得的DSL設計,然后生成可以直接運行的Dynamic代碼,這可能也是元編程的思想之一吧;

圖5:

這可能是未來10年要改變的編程路線吧,我只是猜測;最后軟件將進一步被自定義;

3】鏈式配置Dynamic模式(愛不釋手的思維習慣編程)

再一次提起鏈式編程是覺得它的靈活性無話可說,語言特性本身用在哪里完全需求驅動;把鏈式用來做配置相關的工作非常的合適;我們上面做了元數據配置相關的工作,這里我們試着用鏈式的方法來改善它;

Dynamic類型本身的所有行為屬性都是可以動態構建的,那么我們把它放入鏈式的方法中去,根據不同的參數來實現動態的添加行為;

擴展Dynamic類型需要使用ExpandoObject開始;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DynamicBehavior
 9 {
10     using System;
11     using System.Dynamic;
12 
13     public static class EntityDynamicBehaviorExtent
14     {
15         /// <summary>
16         /// Add dynamic method.
17         /// </summary>
18         /// <param name="entity"></param>
19         /// <returns></returns>
20         public static ExpandoObject AddExten(this ExpandoObject entity)
21         {
22             dynamic dy = entity as dynamic;
23             dy.Add = new Func<ExpandoObject>(() => { Console.WriteLine("add " + entity); return entity; });
24             return entity;
25         }
26         /// <summary>
27         /// where  dynamic method. 
28         /// </summary>
29         /// <typeparam name="T"></typeparam>
30         /// <param name="entity"></param>
31         /// <param name="where"></param>
32         /// <returns></returns>
33         public static ExpandoObject WhereExten<T>(this ExpandoObject entity, Func<T, bool> where)
34         {
35             dynamic dy = entity as dynamic;
36             dy.Where = where;
37             return entity;
38         }
39     }
40 }
View Code

擴展方法需要擴展 ExpandoObject對象,DLR在運行時使用的是ExpandoObject對象實例,所以我們不能夠直接擴展Dynamic關鍵字;

1 Employee employee1 = new Employee() { EId = "Wqp123" };
2                 var dynamicEntity = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee1);
3                 (dynamicEntity as System.Dynamic.ExpandoObject).AddExten().WhereExten<Employee>(emp =>
4                 {
5                     Console.WriteLine("Where Method.");
6                     return emp.EId == "Wqp123";
7                 });
8                 dynamicEntity.Add().Where(employee1);
View Code

 圖6:

紅線部分必須要轉換才能順利添加行為;

4】委托工廠模式(要優於常見的 工廠,概念更加准確,減少污染)

對於工廠模式我們都會熟悉的一塌糊塗,各種各樣的工廠模式我們見的多了,但是這種類型的工廠使用方式你還真的沒見過;其實這種委托是想部分的邏輯交給外部來處理;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DomainModel
 9 {
10     /// <summary>
11     /// Address factory.
12     /// </summary>
13     /// <returns></returns>
14     public delegate Address Factory();
15 
16     /// <summary>
17     /// Employee.<see cref="DomainModel.Employee"/>
18     /// </summary>
19     public class Employee
20     {
21         public Employee() { }
22         /// <summary>
23         /// Mark employee instance.
24         /// </summary>
25         /// <param name="eID"></param>
26         /// <param name="name"></param>
27         /// <param name="sex"></param>
28         /// <param name="addressFactory">address factory.</param>
29         public Employee(string eID, string name, SexType sex, Factory addressFactory)
30         {
31             this.EId = eID;
32             this.Name = name;
33             this.Sex = sex;
34             this.Address = addressFactory();
35         }
36         /// <summary>
37         /// Primary id.
38         /// </summary>
39         public string EId { get; set; }
40 
41         /// <summary>
42         /// Name.
43         /// </summary>
44         public string Name { get; set; }
45 
46         /// <summary>
47         /// Sex.<see cref="DomainModel.SexType"/>
48         /// </summary>
49         public SexType Sex { get; set; }
50 
51         /// <summary>
52         /// Address.
53         /// </summary>
54         public Address Address { get; set; }
55     }
56 }
View Code

我們定義了一個用來創建Employee.Address對象的Factory,然后通過構造函數傳入;

1 Employee employee2 = new Employee("Wqp123", "Wqp", SexType.Male, new Factory(() =>
2                 {
3                     return new Address() { AddressName = "Shanghai" };
4                 }));
View Code

這里純粹為了演示方便,這種功能是不應該在DommianModel中使用的,都是在一些框架、工具中用來做靈活接口用的;

5】規則外掛(視委托為特殊的規則對象原型)

規則外掛其實跟上面的委托工廠有點像,但是絕對不一樣的設計思想;如何將規則外掛出去,放入Cache中讓運行時可以配置這個規則參數;委托是規則的天然宿主,我們只要將委托序列化進Cache就可以對它進行參數的配置;

 1 /*==============================================================================
 2  * Author:深度訓練
 3  * Create time: 2013-08-04
 4  * Blog Address:http://www.cnblogs.com/wangiqngpei557/
 5  * Author Description:特定領域軟件工程實踐;
 6  *==============================================================================*/
 7 
 8 namespace ConsoleApplication1.DomainModel.Specification
 9 {
10     using System;
11     using System.Linq.Expressions;
12 
13     /// <summary>
14     /// Employee add specification.
15     /// </summary>
16     [Serializable]
17     public class EmployeeSpecificationAdd : System.Runtime.Serialization.IDeserializationCallback
18     {
19         /// <summary>
20         /// specification.
21         /// </summary>
22         [NonSerialized]
23         private Func<Employee, bool> _specification;
24         /// <summary>
25         /// Gets specification.
26         /// </summary>
27         public Func<Employee, bool> Specificaion { get { return _specification; } }
28 
29         /// <summary>
30         /// employee.
31         /// </summary>
32         private Employee Employee { get; set; }
33 
34         /// <summary>
35         /// Mark employee specificatoin.
36         /// </summary>
37         /// <param name="employee"></param>
38         public EmployeeSpecificationAdd(Employee employee)
39         {
40             this.Employee = employee;

41             InitSpecification();
42         }
43         /// <summary>
44         /// Is Check.
45         /// </summary>
46         /// <returns></returns>
47         public bool IsCheck()
48         {
49             return _specification(Employee);
50         }
51 
52         public void OnDeserialization(object sender)
53         {
54             InitSpecification();
55         }
56         private void InitSpecification()
57         {
58             this._specification = (emp) =>
59             {
60                 return !string.IsNullOrWhiteSpace(emp.EId) && !string.IsNullOrWhiteSpace(emp.Name);
61             };
62         }
63     }
64 }
View Code

圖7:

注意這里的反序列化接口實現,因為Lambda無法進行序列化,也沒有必要進行序列化;

 1 EmployeeSpecificationAdd specification = new EmployeeSpecificationAdd(employee2);
 2 
 3                 Stream stream = File.Open("specification.xml", FileMode.Create);
 4                 BinaryFormatter formattter = new BinaryFormatter();
 5                 formattter.Serialize(stream, specification);
 6 
 7                 stream.Seek(0, SeekOrigin.Begin);
 8                 specification = formattter.Deserialize(stream) as EmployeeSpecificationAdd;
 9 
10                 stream.Close();
11                 stream.Dispose();
12                 if (specification.IsCheck())
13                 {
14                     Console.WriteLine("Ok...");
15                 }
View Code

既然能將規則序列化了,就可以把它放在任何可以使用的地方了,配置化已經沒有問題了;

示例Demo地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication2.zip

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM