閱讀目錄:html
1.開篇介紹程序員
2.元數據緩存池模式(在運行時構造元數據緩存池)數據庫
2.1.元數據設計模式(抽象出對數據的描述數據)編程
2.2.藉助Dynamic來改變IOC、AOP動態綁定的問題c#
2.3.元數據和模型綁定、元數據應該隱藏在Model背後、元數據與DSL的關係設計模式
3.鏈式配置Dynamic模式(愛不釋手的思惟習慣編程)緩存
4.委託工廠模式(要優於常見的 工廠,概念更加準確,減小污染)數據結構
5.規則外掛(視委託爲特殊的規則對象原型)框架
經過上一篇的「.NET框架設計—常被忽視的C#設計技巧」一文來看,對於框架設計的技巧仍是有不少人比較有興趣的,那麼框架設計思想對於咱們平常開發來講其實並非很重要,可是對於咱們理解框架背後的運行原理相當重要;當咱們使用着LINQ靈活的語法的同時咱們是否能理解它的背後運行原理、設計原理更深一點就是它的設計模式及複雜的對象模型;ide
從一開始學習.NET我就比較喜歡框架背後的設計模型,框架提供給咱們的使用接口是及其簡單的,單純從使用上來看咱們不會隨着對框架的使用時間而增長咱們對框架內部設計的理解,反而會養成同樣哪來即用的習慣,咱們只有去了解、深挖它的內部設計原理纔是咱們長久學習的目標;由於框架的內部設計模式是能夠提煉出來並被總結的;
這篇文章總結了幾個我最近接觸的框架設計思想,能夠稱他們爲模式;因爲時間關係,這裏只是介紹加一個簡單的介紹和示例讓咱們能基本的瞭解它而且能在往後設計框架的時候想起來有這麼一個模式、設計方式能夠借鑑;固然,這每一節都是一個很大主題,用的時候在去細心的分析學習吧;
不少框架都有將特性放在屬性上面用來標識某種東西,可是這種方式使用不當的話會對性能形成影響;再從框架設計原則來說也是對DomainModel極大的污染,從EntityFramework5.0以前的版本咱們就能體會到了,它本來是將部分Attribute加在了Entity上的,可是這畢竟是業務代碼的核心,原則上來講這不能有任何的污染,要絕對的POJO;後來5.0以後就徹底獨立了DomainModel.Entity,全部的管理都在運行時構造綁定關係,由於它有EDMX元數據描述文件;
那麼這些Attribute其實本質是.NET在運行時的一種元數據,主要的目的是咱們想在運行時將它讀取出來,用來對某些方面的判斷;那麼如今的問題是若是咱們每次都去讀取這個Attribute是必需要走反射機制,固然你能夠找一些框架來解決這個問題;(咱們這裏討論的是你做爲開發框架的設計者!)
反射影響性能這不用多講了,那麼常規的作法是會在第一次反射以後將這些對象緩存起來,下次再用的時候直接在緩存中讀取;這沒有問題,這是解決了反射的性能問題,那麼你的Attribute是否還要加在DomainModel中呢,若是加的話隨着代碼量的增長,這些都會成爲後面維護的成本開銷;那麼咱們如何將乾淨的POJO對象提供給程序員用,可是在後臺咱們也能對POJO進行強大的控制?這是不是一種設計問題?
我一直比較關注對象與數據之間的關係,面向對象的這種縱橫向關係如何平滑的與E-R實體關係模型對接,這一直是複雜軟件開發的核心問題;這裏就用它來做爲本章的示例的基本概要;
咱們有一個基本的DomainModel聚合,如何在不影響自己簡潔性的狀況下與E-R關係對接,好比咱們在對聚合進行一個Add操做如何被映射成對數據庫的Insert操做;咱們來看一下元數據設計模式思想;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DomainModel { /// <summary> /// Employee.<see cref="DomainModel.Employee"/> /// </summary> public class Employee { /// <summary> /// Primary id. /// </summary> public string EId { get; set; } /// <summary> /// Name. /// </summary> public string Name { get; set; } /// <summary> /// Sex.<see cref="DomainModel.SexType"/> /// </summary> public SexType Sex { get; set; } /// <summary> /// Address. /// </summary> public Address Address { get; set; } } }
這裏有一個以Employee實體爲聚合根的聚合,裏面包含一些基本的屬性,特別須要強調的是Sex屬性和Address,這兩個屬性分別是Complex類型的屬性; Complex類型的屬性是符合面向對象的須要的,可是在關係型數據庫中是很難實現的,這裏就須要咱們用元數據將它描述出來並能在一些行爲上進行控制;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DomainModel { /// <summary> /// Address . /// </summary> public struct Address { /// <summary> /// Address name. /// </summary> public string AddressName { get; set; } } }
這是Address類型的定義;
namespace ConsoleApplication1.DomainModel { /// <summary> /// Sex type. /// </summary> public enum SexType { Male, Female } }
這是SexType類型的定義;都比較簡單;
只有這樣咱們才能對DomainModel進行大面積的複雜設計,若是咱們不能將數據對象化咱們沒法使用設計模式,也就談不上擴展性;
圖1:
這是咱們的對象模型,那麼咱們如何將它與數據庫相關的信息提取出來造成獨立的元數據信息,對元數據的抽取須要動、靜結合才行;
什麼動、靜結合,咱們是否都會遇見過這樣的問題,不少時候咱們的代碼在編譯時是肯定的,可是有部分的代碼須要在運行時動態的構造,甚至有些時候代碼須要根據當前的IDE來生成才行,可是最終在使用的時候這些在不一樣階段生成的代碼都須要結合起來變成一個完整的元數據對象;
框架在不少時候須要跟IDE結合才能使使用變的順手,好比咱們在開發本身的ORM框架若是不能直接嵌入到VisualStudio中的話,用起來會很不爽;當咱們用本身的插件去鏈接數據庫而且生成代碼的時候,有部分的元數據模型已經在代碼中實現,可是有部分須要咱們動態的去設置才行;
咱們來看一下關於元數據的基礎代碼;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ORM.Meta { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /// <summary> /// Data source context. /// </summary> public abstract class DataBaseContext : List<MetaTable>, IDisposable { /// <summary> /// Data base name. /// </summary> protected string DataBaseName { get; set; } /// <summary> /// Connection string. /// </summary> protected string ConnectionString { get; set; } /// <summary> /// Provider child class add table. /// </summary> /// <param name="table"></param> protected virtual void AddTable(MetaTable table) { this.Add(table); } /// <summary> /// Init context. /// </summary> protected virtual void InitContext() { } public void Dispose() { } } }
這表示數據源上下文,屬於運行時元數據的基礎設施;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ORM.Meta { using System.Collections.Generic; using System.Linq; /// <summary> /// Database Table meta. /// </summary> public class MetaTable : List<MetaColumn> { /// <summary> /// Table name. /// </summary> public string Name { get; set; } /// <summary> /// Entity name. /// </summary> public string EntityName { get; set; } /// <summary> /// Get column by column name. /// </summary> /// <param name="name">Column name.</param> /// <returns><see cref="ORM.MetaColumn"/></returns> public MetaColumn GetColumnByName(string name) { var column = from item in this.ToList() where item.CoumnName == name select item; return column.FirstOrDefault(); } } }
簡單的表示一個Table,裏面包含一系列的Columns;要記住在設計元數據基礎代碼的時候將接口留出來,方便在IDE中植入初始化元數據代碼;
圖2:
到目前爲止咱們都是在爲元數據作基礎工做,咱們看一下有系統生成的聲明的元數據代碼;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.Repository { using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /// <summary> /// IDE Builder. /// </summary> public class DesignBuilder_DataBaseContext : ORM.Meta.DataBaseContext { //this begin IDE builder. protected override void InitContext() { ORM.Meta.MetaTable metaTable = new ORM.Meta.MetaTable() { Name = "TB_Employee", EntityName = "Employee" }; metaTable.Add(new ORM.Meta.MetaColumn() { CoumnName = "EId", DataType = ORM.Meta.DataType.NVarchar }); metaTable.Add(new ORM.Meta.MetaColumn() { CoumnName = "Name", DataType = ORM.Meta.DataType.NVarchar }); metaTable.Add(new ORM.Meta.MetaColumn() { CoumnName = "Sex", DataType = ORM.Meta.DataType.Int }); metaTable.Add(new ORM.Meta.MetaColumn() { CoumnName = "Address", DataType = ORM.Meta.DataType.NVarchar }); this.AddTable(metaTable); } //end } }
我假設這是咱們框架在IDE中生成的部分元數據代碼,固然你能夠用任何方式來存放這些元數據,可是最後仍是要去對象化;
圖3:
這個目錄你能夠直接隱藏,在後臺屬於你的框架須要的一部分,沒有必要讓它污染項目結構,固然放出來也有理由;若是想讓你的LINQ或者表達式能直接穿過你的元數據上下文你須要直接擴展;
static void Main(string[] args) { using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext()) { var employee = from emp in context.Employee where emp.EId == "Wqp123" select emp; } }
這裏全部的代碼看上去很簡單,沒有多高深的技術,這也不是本篇文章的目的,任何代碼都須要設計的驅動才能產生價值,咱們構建的基礎代碼都是元數據驅動;當你在運行時把這些元數據放入Cache,既不須要加Attribute也不須要反射反而活動了更大程度上的控制,可是要想構建一個能用的元數據結構須要結合具體的需求才行;
要想在運行時徹底動態的綁定在編譯時定義的對象行爲是須要強大的IOC框架支撐的,這樣的框架咱們是作不來的或者須要不少精力,得不償失;對於元數據設計須要將AOP經過IOC的方式注入,在使用的時候須要改變一下思路,AOP的全部的切面在編譯時沒法肯定,後期經過IOC的方式將全部的行爲注入;這裏咱們須要使用動態類型特性;
使用Dynamic以後咱們不少以往不能解決問題均可以解決,更向元編程跨進了一步;對於IOC、AOP的使用也將變的很簡單,也有可能顛覆以往IOC、AOP的使用方式;並且動態編程將在很大程度上越過設計模式了,也就是設計模式的使用方式在動態編程中將不復存在了;
using (Repository.DesignBuilder_DataBaseContext context = new Repository.DesignBuilder_DataBaseContext()) { var employees = from emp in context.Employee where emp.EId == "Wqp123" select emp; Employee employee = new Employee() { EId = "Wqp123" }; var entityOpeartion = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee); entityOpeartion.Add(); }
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DynamicBehavior { using System; using System.Dynamic; public class EntityDymanicBehavior { public static dynamic GetEntityBehavior<TEntity>(TEntity entity) { //auto mark entity behavior dynamic dy = new ExpandoObject(); //load meta data mark dynamic behavior dy.Entity = entity; dy.Add = new Action(() => { Console.WriteLine("Action Add " + entity.GetType()); }); return dy; } } }
圖4:
畫紅線的部分是能夠抽取來放入擴展方法Add中的,在構造的內部是徹底能夠進入到元數據緩存池中拿到這些數據而後直接動態生成擴展方法背後的真實方法;
元數據的綁定應該在運行時動態去完成,這點在以往咱們須要大費力氣,經過CodeDom、Emit才能完成,可是如今能夠經過Dynamic、DLR來完成;思惟須要轉變一下,動態編程咱們以往用的最多的地方在JS上,如今能夠在C#中使用,固然你也可使用專門的動態語言來寫更強大的元數據框架,IronRuby、IronPython都是很不錯的,簡單的瞭解過Ruby的元數據編程,很強大,若是咱們.NET程序員眼饞就用Iron…系列;
在開發複雜的動態行爲時儘可能使用元數據設計思想,不要把數據和表示數據的數據揉在一塊兒,要把他們分開,在運行時Dynamic綁定;元數據應該在Model的背後應該在DomainModel的背後;
元數據和DSL有着自然的淵源,若是咱們能把全部的語句組件化就能夠將其封入.NET組件中,在IDE中進行所見即所得的DSL設計,而後生成能夠直接運行的Dynamic代碼,這可能也是元編程的思想之一吧;
圖5:
這多是將來10年要改變的編程路線吧,我只是猜想;最後軟件將進一步被自定義;
再一次提起鏈式編程是以爲它的靈活性無話可說,語言特性自己用在哪裏徹底需求驅動;把鏈式用來作配置相關的工做很是的合適;咱們上面作了元數據配置相關的工做,這裏咱們試着用鏈式的方法來改善它;
Dynamic類型自己的全部行爲屬性都是能夠動態構建的,那麼咱們把它放入鏈式的方法中去,根據不一樣的參數來實現動態的添加行爲;
擴展Dynamic類型須要使用ExpandoObject開始;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DynamicBehavior { using System; using System.Dynamic; public static class EntityDynamicBehaviorExtent { /// <summary> /// Add dynamic method. /// </summary> /// <param name="entity"></param> /// <returns></returns> public static ExpandoObject AddExten(this ExpandoObject entity) { dynamic dy = entity as dynamic; dy.Add = new Func<ExpandoObject>(() => { Console.WriteLine("add " + entity); return entity; }); return entity; } /// <summary> /// where dynamic method. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <param name="where"></param> /// <returns></returns> public static ExpandoObject WhereExten<T>(this ExpandoObject entity, Func<T, bool> where) { dynamic dy = entity as dynamic; dy.Where = where; return entity; } } }
擴展方法須要擴展 ExpandoObject對象,DLR在運行時使用的是ExpandoObject對象實例,因此咱們不可以直接擴展Dynamic關鍵字;
Employee employee1 = new Employee() { EId = "Wqp123" }; var dynamicEntity = DynamicBehavior.EntityDymanicBehavior.GetEntityBehavior<Employee>(employee1); (dynamicEntity as System.Dynamic.ExpandoObject).AddExten().WhereExten<Employee>(emp => { Console.WriteLine("Where Method."); return emp.EId == "Wqp123"; }); dynamicEntity.Add().Where(employee1);
圖6:
紅線部分必需要轉換才能順利添加行爲;
對於工廠模式咱們都會熟悉的一塌糊塗,各類各樣的工廠模式咱們見的多了,可是這種類型的工廠使用方式你還真的沒見過;其實這種委託是想部分的邏輯交給外部來處理;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DomainModel { /// <summary> /// Address factory. /// </summary> /// <returns></returns> public delegate Address Factory(); /// <summary> /// Employee.<see cref="DomainModel.Employee"/> /// </summary> public class Employee { public Employee() { } /// <summary> /// Mark employee instance. /// </summary> /// <param name="eID"></param> /// <param name="name"></param> /// <param name="sex"></param> /// <param name="addressFactory">address factory.</param> public Employee(string eID, string name, SexType sex, Factory addressFactory) { this.EId = eID; this.Name = name; this.Sex = sex; this.Address = addressFactory(); } /// <summary> /// Primary id. /// </summary> public string EId { get; set; } /// <summary> /// Name. /// </summary> public string Name { get; set; } /// <summary> /// Sex.<see cref="DomainModel.SexType"/> /// </summary> public SexType Sex { get; set; } /// <summary> /// Address. /// </summary> public Address Address { get; set; } } }
咱們定義了一個用來建立Employee.Address對象的Factory,而後經過構造函數傳入;
Employee employee2 = new Employee("Wqp123", "Wqp", SexType.Male, new Factory(() => { return new Address() { AddressName = "Shanghai" }; }));
這裏純粹爲了演示方便,這種功能是不該該在DommianModel中使用的,都是在一些框架、工具中用來作靈活接口用的;
規則外掛其實跟上面的委託工廠有點像,可是絕對不同的設計思想;如何將規則外掛出去,放入Cache中讓運行時能夠配置這個規則參數;委託是規則的自然宿主,咱們只要將委託序列化進Cache就能夠對它進行參數的配置;
/*============================================================================== * Author:深度訓練 * Create time: 2013-08-04 * Blog Address:http://www.cnblogs.com/wangiqngpei557/ * Author Description:特定領域軟件工程實踐; *==============================================================================*/ namespace ConsoleApplication1.DomainModel.Specification { using System; using System.Linq.Expressions; /// <summary> /// Employee add specification. /// </summary> [Serializable] public class EmployeeSpecificationAdd : System.Runtime.Serialization.IDeserializationCallback { /// <summary> /// specification. /// </summary> [NonSerialized] private Func<Employee, bool> _specification; /// <summary> /// Gets specification. /// </summary> public Func<Employee, bool> Specificaion { get { return _specification; } } /// <summary> /// employee. /// </summary> private Employee Employee { get; set; } /// <summary> /// Mark employee specificatoin. /// </summary> /// <param name="employee"></param> public EmployeeSpecificationAdd(Employee employee) { this.Employee = employee; InitSpecification(); } /// <summary> /// Is Check. /// </summary> /// <returns></returns> public bool IsCheck() { return _specification(Employee); } public void OnDeserialization(object sender) { InitSpecification(); } private void InitSpecification() { this._specification = (emp) => { return !string.IsNullOrWhiteSpace(emp.EId) && !string.IsNullOrWhiteSpace(emp.Name); }; } } }
圖7:
注意這裏的反序列化接口實現,由於Lambda沒法進行序列化,也沒有必要進行序列化;
EmployeeSpecificationAdd specification = new EmployeeSpecificationAdd(employee2); Stream stream = File.Open("specification.xml", FileMode.Create); BinaryFormatter formattter = new BinaryFormatter(); formattter.Serialize(stream, specification); stream.Seek(0, SeekOrigin.Begin); specification = formattter.Deserialize(stream) as EmployeeSpecificationAdd; stream.Close(); stream.Dispose(); if (specification.IsCheck()) { Console.WriteLine("Ok..."); }
既然能將規則序列化了,就能夠把它放在任何可使用的地方了,配置化已經沒有問題了;
示例Demo地址:http://files.cnblogs.com/wangiqngpei557/ConsoleApplication2.zip