zoukankan      html  css  js  c++  java
  • .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)  使用反射的源代码 下载

  • 相关阅读:
    Django实战—权限管理系统rbac组件实现
    Django模型层的DateTimeField、DateField字段设置时间格式为显示当前年月日时分秒的时间格式及时区
    mysql数据库删除一条数据之后,主键id不连续的问题解决
    python多继承(super().__init__())、*args和**kwargs、
    Django数据库操作中You are trying to add a non-nullable field 'name' to contact without a default错误处理
    配置等模版
    【SSM】(一)SSM整合-增删改查书籍
    【SpringMVC】(八)使用Ajax前后端传数据&不使用Ajax
    【SpringMVC】@RequestMapping注意点
    leetcode (堆->中级) 264,313,347,373,378,767,1642,973,1673,743,787
  • 原文地址:https://www.cnblogs.com/confach/p/135303.html
Copyright © 2011-2022 走看看