[.net 物件導向程式設計進階] (21) 反射(Reflection)(下)設計模式中利用反射解耦
本節導讀:上篇文章簡單介紹了.NET物件導向中一個重要的技術反射的基本應用,它可以讓我們動態的呼叫一個程式集中的成員,本篇文章將介紹如何將這一重要特性應用到設計模式中,達到swich……case,if……else帶來的耦合問題,讓我們的程式碼更漂亮,更靈活。
讀前必備:
[.net 物件導向程式設計基礎] (9) 類和類的例項
[.net 物件導向程式設計基礎] (16) 介面
[.net 物件導向程式設計進階] (15) 快取(Cache)(二) 利用快取提升程式效能
[.net 物件導向程式設計進階] (20) 反射(Reflection)(上)利用反射技術實現動態程式設計
1.從一個例項解決問題開始
為了說的通俗易懂一些,本篇從一個常用例項開始,其實用過程式碼生成器的同學肯定很瞭解工廠反射模式了,這種模式應用在資料層面,主要解決了一個問題,那就是可以動態更換資料庫而不需要改動程式碼,讓我們的程式碼解耦。下面我們一步一步改進程式碼,最終實現我們的目標——動態資料訪問層設計。
1.1最簡單的資料訪問設計
我們建立兩個類,一個User類,一個UserSqlServer類,實現如下:
User.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class User { public int Id { get; set; } public string Name { get; set; } } }
UserSqlServer.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class SqlServerUser { public void InserUser(User user) { Console.WriteLine("在SqlServer中新增一個使用者"); } public User GetUser(int id) { Console.WriteLine("在SqlServer中通過id獲得一個使用者記錄,Id:"+id); return null; } } }
輸出程式碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class Program { static void Main(string[] args) { User user = new User(); SqlServerUser li = new SqlServerUser(); li.InserUser(user); li.GetUser(1); Console.ReadKey(); } } }
輸出結果如下:
1.2 利用工廠模式來改進
上面的程式碼,讓我們的程式碼死死的繫結在SqlServer上了,如果我們要換個Access資料庫,換個Oracle呢,於是我們利用工廠模式改進一下。
首先說一下什麼是工廠模式,簡單說就是通過一個抽象出一個工廠類,可以例項不同的類,來達到解耦。
為了能讓程式碼寫的活一些,我們用工廠模式來改進上面的程式碼,假如除了User類之外,還有一個Product類,我需要程式碼實現能隨時更換資料庫。為了達到這個要求,我們建立了介面IUser,IProduct,然後分別用三種資料庫的操作類來實現這兩個介面,最後我們建立一個工廠類Factory,在工廠類中,我們可以更換資料庫,來動態呼叫不同資料層。下面是類圖:
程式集結構如下:
下面是具體程式碼:
User.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class User { public int Id{get; set;} public string Name{get; set;} } }
Product.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class Product { public int ProductId { get; set; } public string productName { get; set; } } }
Factory.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class Factory { //資料庫選擇(Access\SqlServer\Oracle) private static readonly string db = "Access"; public static IUser CreateUser() { IUser result = null; switch (db) { case "Access": result = new DataAccessUser(); break; case "SqlServer": result = new DataSqlServerUser(); break; case "Oracle": result = new DataOracleUser(); break; } return result; } public static IProduct CreateProduct() { IProduct result = null; switch (db) { case "Access": result = new DataAccessProduct(); break; case "SqlServer": result = new DataSqlServerProduct(); break; case "Oracle": result = new DataOracleProduct(); break; } return result; } } }
IUser.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { interface IUser { void InserUser(User user); User GetUser(int id); } }
IProduct.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { interface IProduct { void InsertProduct(Product product); Product GetProduct(int id); } }
DataAccessUser.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataAccessUser : IUser { public void InserUser(User user) { Console.WriteLine("在Access中新增一個使用者"); } public User GetUser(int id) { Console.WriteLine("在Access中通過id獲得一個使用者記錄,Id:" + id); return null; } } }
DataSqlServerUser.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataSqlServerUser : IUser { public void InserUser(User user) { Console.WriteLine("在SqlServer中新增一個使用者"); } public User GetUser(int id) { Console.WriteLine("在SqlServer中通過id獲得一個使用者記錄,Id:" + id); return null; } } }
DataOracleUser.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataOracleUser : IUser { public void InserUser(User user) { Console.WriteLine("在Oracle中新增一個使用者"); } public User GetUser(int id) { Console.WriteLine("在Oracle中通過id獲得一個使用者記錄,Id:" + id); return null; } } }
DataAccessProduct.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataAccessProduct : IProduct { public void InsertProduct(Product product) { Console.WriteLine("在Access中新增一個產品"); } public Product GetProduct(int id) { Console.WriteLine("在Access中通過id獲得一個產品記錄,Id:" + id); return null; } } }
DataSqlServerProduct.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataSqlServerProduct:IProduct { public void InsertProduct(Product product) { Console.WriteLine("在SqlServer中新增一個產品"); } public Product GetProduct(int id) { Console.WriteLine("在SqlServer中通過id獲得一個產品記錄,Id:" + id); return null; } } }
DataOracleProduct.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class DataOracleProduct: IProduct { public void InsertProduct(Product product) { Console.WriteLine("在Oracle中新增一個產品"); } public Product GetProduct(int id) { Console.WriteLine("在Oracle中通過id獲得一個產品記錄,Id:" + id); return null; } } }
應用程式呼叫入口:
Program.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DataBase { class Program { static void Main(string[] args) { User user = new User(); IUser userData = Factory.CreateUser(); userData.InserUser(user); userData.GetUser(1); Product product = new Product(); IProduct productData = Factory.CreateProduct(); productData.InsertProduct(product); productData.GetProduct(1); Console.ReadKey(); } } }
執行結果如下:
1.3 利用工廠反射模式繼續改進
上面的設計,已經能夠實現多個資料庫切換了,但是我們依然要改動工廠類的程式碼,來實現這一目標。並且還是有很多case語句來實現不同資料庫的呼叫,這樣如果不僅僅只有User和Product類,那麼程式碼量也是相當大的。我們利用本節重點要說的.NET特性,就可以達成目標了。
先看改進後的類圖:
增加了一個快取類,可以提高反射執行效率,改進了工廠類Factory,在工廠中使用反射,從而不再需要使用switch……case,if……else來寫不同資料庫的選擇。為了實現不需要改動程式碼而動態變更資料庫,我們使用了配置檔案,在程式執行過程中,只需要更改配置檔案中的資料庫型別和對應的連線字元,即可以動態實現了切換到不同資料庫。下面是改進後的工廠類和配置檔案
Factory.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Configuration; using System.Reflection; namespace DataBase { class Factory { //能過Config檔案來更改資料庫 private static readonly string db = System.Configuration.ConfigurationManager.AppSettings["db"]; //程式集 private static readonly string AssemblyPath = "DataBase"; /// <summary> /// 建立物件或從快取獲取 /// </summary> public static object CreateObject(string AssemblyPath, string ClassNamespace) { object objType = MyCache.GetCache(ClassNamespace);//從快取讀取 if (objType == null) { try { objType = Assembly.Load(AssemblyPath).CreateInstance(ClassNamespace);//反射建立 MyCache.SetCache(ClassNamespace, objType);// 寫入快取W } catch (Exception ex) { } } return objType; } /// <summary> /// 建立資料層介面IUser /// </summary> public static IUser CreateUser() { string ClassNamespace = AssemblyPath + ".Data" + db + "User"; object objType = CreateObject(AssemblyPath, ClassNamespace); return (IUser)objType; } /// <summary> /// 建立資料層介面IUser /// </summary> public static IProduct CreateProduct() { string ClassNamespace = AssemblyPath + ".Data" + db + "Product"; object objType = CreateObject(AssemblyPath, ClassNamespace); return (IProduct)objType; } } }
下面是快取類(需要引用System.Web.dll)
MyCache.cs
using System; using System.Web; namespace DataBase { /// <summary> /// 快取相關的操作類 /// </summary> public class MyCache { /// <summary> /// 獲取當前應用程式指定CacheKey的Cache值 /// </summary> /// <param name="CacheKey"></param> /// <returns></returns> public static object GetCache(string CacheKey) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; return objCache[CacheKey]; } /// <summary> /// 設定當前應用程式指定CacheKey的Cache值 /// </summary> /// <param name="CacheKey"></param> /// <param name="objObject"></param> public static void SetCache(string CacheKey, object objObject) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert(CacheKey, objObject); } /// <summary> /// 設定當前應用程式指定CacheKey的Cache值 /// </summary> /// <param name="CacheKey"></param> /// <param name="objObject"></param> public static void SetCache(string CacheKey, object objObject, DateTime absoluteExpiration, TimeSpan slidingExpiration) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert(CacheKey, objObject, null, absoluteExpiration, slidingExpiration); } } }
配置檔案:
App.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" /> </startup> <appSettings> <!--資料庫型別選擇 ConnectionType:Access|SqlServer|Oracle|MySql|Xml --> <add key="db" value="SqlServer"/> <!-- 資料庫連線字串 --> <add key="ConStrAccess" value="Provider=Microsoft.ACE.OLEDB.12.0;Data Source=D:aa.accdb;Persist Security Info=False"/> <add key="ConStrSqlServer" value="server=(local)\SQLEXPRESS;database=data;uid=sa;pwd=123456"/> <add key="ConStrOracel" value="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=local)(PORT=1521)) User ID=scott;Password=tiger;Unicode=True"/> <add key="ConStrMySql" value="server=localhost;user id=root;password=123456;database=ABC; pooling=true;"/> <add key="ConStrXml" value="C:/Data.xml"/> </appSettings> </configuration>
下面是我們在app.config中選擇了其中一個資料庫後的輸出結果:
我們更改為Oracle看看輸出結果:
2.本節要點:
可以看到,完全達到了我們的目標:
A.多資料庫切換
B.切換資料庫不需要改動程式碼,從而減少重新編譯重新部署的麻煩
C.各個資料庫操作類相互獨立,易於維護
D.使用反射解決了switch if等分支判斷帶來的耦合。
==============================================================================================
<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>
<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>
<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>
==============================================================================================