[.net 面向对象程序设计进阶] (21) 反射(Reflection)(下)设计模式中利用反射解耦
本节导读 : 上篇文章简单介绍了 .NET 面向对象中一个重要的技术反射的基本应用,它可以让我们动态的调用一个程序集中的成员,本篇文章将介绍如何将这一重要特性应用到设计模式中,达到 swich …… case,if …… else 带来的耦合问题,让我们的代码更漂亮,更灵活。
为了说的通俗易懂一些,本篇从一个常用实例开始,其实用过代码生成器的同学肯定很了解工厂反射模式了,这种模式应用在数据层面,主要解决了一个问题,那就是可以动态更换数据库而不需要改动代码,让我们的代码解耦。下面我们一步一步改进代码,最终实现我们的目标——动态数据访问层设计。
我们创建两个类,一个 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;         }      } }  View Code   输出代码如下:
  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();         }     } }  View Code   输出结果如下:
 
 
上面的代码,让我们的代码死死的绑定在 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;}     } }  View Code   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; }     } }  View Code   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;         }      } }  View Code   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);            } }  View Code   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);     } }  View Code   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;         }     }   }  View Code   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;         }     } }  View Code   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;         }     } }  View Code   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;         }     } }  View Code   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;         }     } }  View Code   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;         }     } }  View Code   应用程序调用入口:
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看看输出结果:
  
  
可以看到,完全达到了我们的目标:
A.多数据库切换
B.切换数据库不需要改动代码,从而减少重新编译重新部署的麻烦
C.各个数据库操作类相互独立,易于维护
D.使用反射解决了switch if等分支判断带来的耦合。