.NET的反射在软件设计上的应用

.NET的反射在软件设计上的应用

  问题的引出

前一段时间,我在写一个跟踪和管理Bug的程序,编程语言为C#.

本软件采用经典的多层架构.我将软件分成UI Layer, Bussiness Layer, Data Access Layer.

问题就是出现这里.我最开始的写法是,Business Layer定义了几个类,假设为DataProvider,User,Login,Data Access Layer定义了SqlDataProvider.

他们的关系如下:   
         

DataProvider为一个纯虚函数.

SqlDataProvder继承DataProvider..

User为用户信息类.

Login为登陆信息类,其中有一方法CheckUser()验证用户的有效性.

并且我对3层各自建立了一个工程,也就是说这个Solution包括3个工程,假设BugUI,BugBusiness,BugData.Build之后生成3DLL文件,分别是UI.dll,BugBusiness.dll,BugData.dll.

其中DataProvider的代码如下:

using System; 
namespace BugBusiness 
{  
                
/// <summary> 
                
/// A data provider,and it is an abstract class,so there is  a class have to inhrits from it!  
                
/// </summary> 
 
                
public abstract class DataProvider  
                
{  
                                
/// <summary>  
                                
/// To check if user is valid  
                                
/// </summary>  
                                
/// <param name="name">user name</param>  
                                
/// <param name="password">user password</param>  
                                
/// <returns>  
                                
/// True if user is valid,or else return false  
                                
///</returns> 
 
                                
public abstract int CheckUser(User user);  
                }
 

public class User  
{  
                
private string name;  
                
private string password;  
                
/// <summary>  
                
/// Property Name  
                
/// </summary> 
 
                
public string  Name  
                
{  
                                
get  
                                
{  
                                        
return name;  
                                }
  
                                
set 
                                
{  
                                                name
=value; 
                                }
  
                }
 
                 
/// <summary> 
                
/// Property Password 
                
/// </summary> 
 
                
public string Password 
                

                                
get  
                                

                                                
return password; 
                                }
 
                                
set 
                                

                                                password
=value; 
                                       }
 
                } 
}
 

                
public class Login 
                

                                
public static bool IsValidUser(User user) 
                                

                                                BugData.SqlDataProvider dp
=new BugData.SqlDataProvider(); 
                                                if(
dp.CheckUser(user)==1) return true;
                                                    return false
                                }
 
                }
 
                }
 

 

 

SqlDataProvider的代码如下:

using System;
using BugBusiness;
namespace BugData
{
    
/// <summary>
    
/// Summary description for Class1.
    
/// </summary>

    public class SqlDataProvider:BugBusiness.DataProvider
    
{// <summary>
        /// This method is to check the user name/password is valid
        
/// </summary>
        
/// <param name="user">
        
/// the user to check
        
/// </param>
        
/// <returns>
        
/// 1 ,user name/password is valid
        
/// 2 ,not valid
        
/// </returns>
        
/// <remarks>This method is to simulate accessing database</remarks>

        public override int  CheckUser(BugBusiness.User user)
        
{
            
if(user.Name=="Name" && user.Password=="123")
                
return 1;
            
return 2;
        }


    }

}

 

      咋一看起来这个没有错.是呀,在语法上面没有错,但是在编译的时候除了问题.为什么?

问题的解释

上面的代码编译会出现问题,为什么?经过仔细的琢磨,才明白其中的缘由.

让我们先看看其中的类图,不知道发现了什么.也许你很容易看出问题, 但是在实际解决的时候可能就不会注意这个问题了.

 本类图是一个很糟糕的设计.分析如下:

我们可以从类图和代码中发现,设计致力于Business层和Data.在这里我们只罗列出一个很简单的问题, 即验证用户的有效型. LoginUser都在Business, 只有SqlDataProviderData.其实你很快就会发现Business层调用了Data, Data层又调用了Business.调用图如下.

 

从上图可以看出,调用是双向的.现在你可以看出其中的问题的吧.如果你还没有看出,在心里面可能已经有了一个印象,隐隐约约感到其中的不合理之处.

说了这么多,那么到底会产生什么不良的影响呢?

这种设计比较晦涩,导致结构层次不清,往往难以维护,有时甚至是出错.这样说可能是有点抽象,那就具体一点说吧,以前面的Case为例,举出其中的影响.

假设Business层的单独的Project编译的程序集DLLBugBusiness.DLL,Data层的工程编译的程序集是BugData.DLL.同时假设BugBusiness.DLL的版本是0.9.0,BugData.DLL的版本也是0.9.0.Ok,这里是起点.我接下来再编译一次,BugBusiness.dll版本变为0.9.1,BugData的版本也变为0.9.1.

这里就出现了一个问题.BugBusiness.dll是基于0.9.0BugData.dll,但是现在确实0.9.1.同理,BugData.dll本应基于0.9.0BugBusiness.dll,现在却是0.9.1.我们可以用下面的图表示:

说明:实线表示应该调用的

         虚线表示实际调用的

 

如果在.NET编译,会报出版本调用不一致的错误.即使不报错误,在以后的维护中够我们受的了.本来分层就是为了使项目简单,易于维护,到现在却事与愿违.

 

问题的解决

方案1:反射

既然原因已经知道,那么该如何解决呢?有人肯定会问,Business层的DataProvider好像没有多大作用,我之所以设计这个类,就是考虑到了工层的可扩展性,我现在用的是Microsoft SQL Server,如果哪天我用Oracle,My Sql,甚至其他,只需要继承DataProvider即可,例如OracleDataProvider,这样你只需要在写配置文件的时候说明用到的数据库是Oracle.

我想解决的方法就是避免双向调用,那么是去掉Business层调用Data层呢,还是去掉Data层调用Business层呢?显然Data层调用Business层是不可避免的,那么只有去掉Business层调用Data,但是你可能就会问,我怎么去掉呢,Login肯定会用到SqlDataProvider?问题就是在这里了.

 我不知道你发现DataProvider这个类没有,要知道DataProvider是一个abstract类呀.根据面向对象的性质,调用抽象类时,其实是调用其实现它的子类,即调用SqlDataProvider.现在应该明白了吧.

你很有可能晦写出如下代码:

 DataProvider dp=new DataProvider();

很遗憾,编译器肯定会告诉你,你不可以实现一个抽象类的实例.怎么样,是不是有些晕.但是既然这样,我们该如何实现呢?答案是反射.我们可以利用反射来创建一个实例.

如何创建,只需在DataProvider增加一个静态的实例方法Instance(),参考下面代码:

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;        
public static DataProvider Instance(string connectionString,string databaseOwner,string providerTypeName)
        
{
            Type type 
= null;
            
            type 
= Type.GetType( providerTypeName );

            
// Insert the type into the cache
            
//
            Type[] paramTypes = new Type[2];
            paramTypes[
0= typeof(string);
            paramTypes[
1= typeof(string);
            
object[] paramArray = new object[2];
            paramArray[
0= databaseOwner;
            paramArray[
1= connectionString;

            
return (DataProvider)(  ((ConstructorInfo)type.GetConstructor(paramTypes)). Invoke(paramArray) );
        }

        

 

SqlDataProvider里增加:

                public SqlDataProvider(string connectionString,string databaseOwner)

                                {

                         }

请注意第4

using System.Reflection;

它引用了反射命名空间.如何进行反射,下面解释一下,

type = Type.GetType( providerTypeName );

它得到构造的实例的类型,在这里可以是SqlDataProvider.因为在SqlDataProvider构造时有两个参数,并且是string类型,所以paramTypes都为string类型,如果是int,请用typeof(int);定义了类型之后,最后传入参数的值,databaseOwner;connectionString.这些准备完之后,现在正式开始构造,利用ConstructorInfo这个类,使用前面定义的参数类型数组和参数值数组即可创建.更多的详情参考MSDN.

 有了实例化之后,应该如何调用,请看如下代码:

public class Login
    
{
        
public static bool IsValidUser(User user)
        
{
        
            BugBusiness.DataProvider  dp 
=BugBusiness.DataProvider.Instance("Server=localhost;uid=sa;pwd=;database=northwind","dbo",
                                                                                                              
"BugData.SqlDataProvider,BugData" );
            
return dp.CheckUser(user);
        }

    }

这样即可.

  最后,类图可以为:


方案
2:固定程序集的版本

  这个方案就我个人而言我不太赞成,但是也是最简单的办法,就是控制版本,比如一直是0.9.0.如何控制,很简单,在每个工程里都有一个AssemblyInfo.cs,里面有一行

[assembly: AssemblyVersion("1.0.*")]

的代码.这个就是版本,你只需要写入固定的版本,那么无论怎么编译,它的版本都不会改变

应用

 关于反射的应用非常多.比如微软提供的PetShopDuwamish就用到类似的反射性质.许多著名的开源项目如AspNetForums也用到了.

感谢

在此,感谢我的3位同事,Ming Wang, Nancy Huang,Nanco Xing. 

附录: 源代码
1)  没有使用反射的源代码 下载

2)  使用反射的源代码 下载

posted @ 2005-04-11 09:10  张太国  阅读(6613)  评论(21编辑  收藏  举报