抽象工厂+反射+依赖注入 实现对数据访问层和业务逻辑层的优化

分层思想的一个核心就是部件化,各个层之间是相互独立的,每一层可以随便抽取换成一个其他语言的版本,但只要与相应的接口吻合就行。

我用的三层架构大致是这样的,基本的三层就不说了,然后分别为业务逻辑层和数据访问层定义一个接口,由具体的那个层来实现,问题产生了,由谁来指定程序使用哪个具体的对象来实现相应接口?

解决这个问题,我应用的是抽象工厂模式。分别为业务逻辑层和数据访问层添加一个抽象工厂。具体架构还是看下图吧。

这里的Utility是一个工具类,在下文中会提到。

学过设计模式的人都应该听过反射技术,但是一个系统中用到的类很多,需要对每一个类进行实例化,如果仅利用抽象工厂+反射模式,重复的代码比较多,如果哪一天整个DAL层发生变更,那么就要在代码修改一个用到的地方,不仅不容易维护,而且还很容易出错,未解决这个问题,对程序作了一个优化——用到依赖注入。还是看看代码吧。

1、先看看依赖注入的容器:这里我把这个注入容器放到了工具类中,刚开始学习设计模式,不知道是否合理,欢迎高手们指点。

[vb]  view plain copy
  1. Imports System.Configuration  
  2. Imports System.Reflection  
  3. Public Class DependencyInjector  
  4.   
  5.     ''' <summary>  
  6.     ''' 利用反射机制,取得数据访问层对象  
  7. ''' </summary>  
  8. ''' <param name="className">传入数据访问层中要实例化的类的名称</param>  
  9. ''' <returns>指定的数据访问层的类</returns>  
  10. ''' <remarks></remarks>  
  11.     Function GetDALObject(ByVal className As StringObject  
  12.         Dim dal Object  
  13.         Dim dalName String  
  14. Dim fullClassName String  
  15. Dim dalObj          '通过配置文件的指定要应用的DAL层  
  16.         dal = System.Configuration.ConfigurationManager.AppSettings("DAL")  
  17.   
  18.         'dalName就是常说的用用程序的名称  
  19.         dalName = dal.ToString  
  20. '命名空间+类的名称,指明要实例化的类的路径  
  21.         fullClassName = dalName + "." + className  
  22. '通过反射,取得数据访问层对象  
  23.         dalObj = Assembly.Load(dalName).CreateInstance(fullClassName)  
  24. '返回指定的对象  
  25. Return dalObj  
  26.     End Function  
  27. '''取得指定业务逻辑层的指定类  
  28. ''' <param name="className">要应用的业务逻辑层的具体类的名称</param>  
  29. ''' <returns>指定的业务逻辑层的类(对象)</returns>  
  30. Function GetBLLObject(Dim bll Dim bllName Dim bllObj '从配置文件中读取业务逻辑名称  
  31.         bll = System.Configuration.ConfigurationManager.AppSettings("BLL")  
  32.         bllName = bll.ToString  
  33.         fullClassName = bllName + "." + className  
  34. '利用反射取得业务逻辑层对象  
  35.         bllObj = Assembly.Load(bllName).CreateInstance(fullClassName)  
  36. Return bllObj  
  37. Function  
  38. Class  

2、相关配置文件

[html]  copy
    <appSettings>  
  1.     add key="connStr"  value="Persist Security Info=true;Data Source=*****;Initial Catalog=Charge_Sys_SelfDesign;User ID=sa;PWD=****;" />  
  2.     add key="DAL"  value="DAL" />  
  3. add key="BLL"  value="BLL" />      
  4.   </>  

3、业务逻辑层工厂:这里以在工厂中生产一个UserBLL类为例,在工厂中添加createuserBLL()方法,理论上讲,业务逻辑层有多少个类在此工厂中就要有多少个相应的方法,但是针对不同语言写的或者是不同的程序员用同一种语言写的同一层的代码(但都实现了程序指定的接口),我们在给类起名字的时候,只要用相同的类名就可以通过仅修改配置文件,达到换层的目的,而无需在工厂中改动任何代码。比如说,我现在要把DAL层换成AccessDAL,那么仅需要做如下修改即可。

copy
    add key="DAL"  value="AccessDAL" />  

这就是分层的好处,当然也是面向对象思想的优势了。

上面主要是解释了一下配置文件,来看看业务逻辑工厂代码

copy
    Imports IBLL  
  1. Imports Utility  
  2. Imports System.Windows.Forms  
  3. Class CreateBLL  
  4. Private dependecy New Utility.DependencyInjector  
  5. ''' <summary>  
  6. ''' 生成用户业务逻辑层访问对象  
  7. ''' </summary>  
  8. ''' <returns></returns>  
  9. Function createuserBLL() As IBLL.IUserBLL  
  10. Try  
  11.             '如果不用注入,需要重复N次BLL,  
  12.             'Return CType(Assembly.Load("BLL").CreateInstance("BLL.UserBLL"), IBLL.IUserBLL)  
  13. '优化后只需指明具体类名,如果要换层,只需到配置文件中做简单修改即可,程序代码清晰,不宜出错。  
  14.             Return CType(dependecy.GetBLLObject("UserBLL"), IBLL.IUserBLL)  
  15. Catch ex As Exception  
  16.             MsgBox(ex.Message)  
  17.             nothing  
  18. Class  

4、数据访问层工厂代码类似:

copy
    Imports Utility  
  1. Imports System.Configuration  
  2. Imports System.Reflection  
  3. Imports IDAL  
  4. Class CreateDAL  
  5. Private dependency New Utility.DependencyInjector  
  6. ''' 生成用户指定的数据访问层对象  
  7. ''' <returns></returns>  
  8. ''' <remarks></remarks>  
  9. Function createuserDAL() As IDAL.IUserDAL  
  10. Try  
  11. 'Return CType(Assembly.Load("DAL").CreateInstance("DAL.UserDAL"), IDAL.IUserDAL)  
  12. CType(dependency.GetDALObject("UserDAL"), IDAL.IUserDAL)  
  13. As Exception  
  14.             MessageBox.Show(ex.Message)  
  15. nothing  
  16. Class  

5、业务逻辑层接口代码比较简单,只需要定义一些相关接口方法即可,但是这里面体现了系统的架构,看似代码简单,实则反应程序员的架构水平。

copy
    Interface IUserBLL  
  1. '返回用户登录的身份级别,在接口的实现中要验证用户名和密码  
  2. Function LogIn(ByVal modelUser As Model.User) Interface  

6、数据访问层接口:

copy
    Interface IUserDAL  
  1. '获取用户ID  
  2. Function GetID('获取用户密码  
  3. Function GetPwd('获取用户级别  
  4. Function GetLevel(Interface  

7、业务逻辑层:

copy
    Imports DALFactory  
  1. Imports IBLL  
  2. Imports IDAL  
  3. Imports Model  
  4. Imports System.Data.sqlClient  
  5. Imports System.Collections.Generic  
  6. Class UserBLL  
  7. Implements IBLL.IUserBLL  
  8. Private dalFactory New DALFactory.CreateDAL  
  9. ''' 先判断用户名和密码是否正确,然后返回用户级别  
  10. ''' <param name="modelUser">用户实体类</param>  
  11. ''' <returns>用户级别</returns>  
  12. String Implements IBLL.IUserBLL.LogIn  
  13. Dim userLevel If dalFactory.createuserDAL.GetID(modelUser) = "" Then  
  14.                 MsgBox("用户名错误!")  
  15.                                  Exit If  
  16. If dalFactory.createuserDAL.GetPwd(modelUser) = "" Then  
  17.                 MsgBox("密码名错误!")  
  18. If  
  19. '通过数据访问层工厂指定实现数据访问层接口的具体的数据访问层以及该层的具体类  
  20.             userLevel = dalFactory.createuserDAL.GetLevel(modelUser)  
  21. Return userLevel  
  22. Class  

 

8、数据访问层:

copy
    Class UserDAL  
  1. '实现数据访问层的接口  
  2. Implements IDAL.IUserDAL  
  3. Private sqlHelp New Utility.sqlServerDALHelp  
  4. ''' 读取用户ID  
  5. ''' <param name="modelUser">用户实体类</param>  
  6. ''' <returns>用户ID</returns>  
  7. Implements IDAL.IUserDAL.GetID  
  8. Dim User_ID Dim conn New sqlConnection(sqlHelp.connStr)  
  9. Dim spName          spName = "proc_GetUserID"  
  10. Dim cmd New sqlCommand(spName, conn)  
  11.         cmd.CommandType = CommandType.StoredProcedure  
  12. Dim ParaAs sqlParameter  
  13.         Param = New sqlParameter("@User_ID"sqlDbType.VarChar)  
  14.         Param.Value = modelUser.User_ID  
  15.         cmd.Parameters.Add(Param)  
  16.             conn.open()  
  17.             User_ID = cmd.ExecuteScalar.ToString  
  18. Return User_ID  
  19. Throw New Exception(ex.Message)  
  20. Finally  
  21.             conn.Close()  
  22.             cmd.dispose()  
  23.             cmd = Return User_ID  
  24. ''' 读取用户密码  
  25. ''' <returns>密码</returns>  
  26. Implements IDAL.IUserDAL.GetPwd  
  27. Dim user_Pwd          spName = "proc_GetUserPwd"  
  28. As sqlParameter  
  29.         Param = New sqlParameter("@User_Pwd"sqlDbType.VarChar)  
  30.         Param.Value = modelUser.User_Pwd  
  31.         cmd.Parameters.Add(Param)  
  32.             user_Pwd = cmd.ExecuteScalar.ToString  
  33.             MsgBox(ex.Message)  
  34. New Exception(ex.Message)  
  35. Finally  
  36.             conn.Close()  
  37.             cmd.dispose()  
  38.             cmd = Return user_Pwd  
  39. ''' 读取用户身份级别  
  40. ''' <returns>身份级别</returns>  
  41. Implements IDAL.IUserDAL.GetLevel  
  42. Dim User_Level          spName = "proc_GetUserLevel"  
  43. New sqlConnection(sqlHelp.connStr)  
  44.         cmd.CommandType = CommandType.StoredProcedure  
  45.             conn.open()  
  46.             User_Level = cmd.ExecuteScalar.ToString  
  47. Return User_Level  
  48. Class  

9、总算到UI层了:注意这里还没有添加针对用户输入合法性的验证,如,用户名、密码输入是否为空,是否符合指定格式等。

copy
    Imports Model  
  1. Imports System.Collections.Generic  
  2. Imports BLLFactory  
  3. Class frmlogIn  
  4. Private bllFactory New BLLFactory.CreateBLL  
  5. Private Sub btnLogIn_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles btnLogIn.Click  
  6. Dim modelUser New Model.User  
  7.         modelUser.User_ID = txtUserID.Text  
  8.         modelUser.User_Pwd = txtUserPwd.Text  
  9. '通过业务逻辑工厂指定业务逻辑接口要使用的具体的业务逻辑层中的具体类  
  10.         userLevel = bllFactory.createuserBLL.LogIn(modelUser)  
  11. Select Case userLevel  
  12. Case "一般用户"  
  13. Case "操作员"  
  14. Case "管理员"  
  15.                 frmMDIParentForm.Show()  
  16.               
  17. Case Else  
  18.                 MsgBox("未知错误!")  
  19. Sub  
  20. Select  
  21. Sub btnExit_Click(Handles btnExit.Click  
  22.         txtUserID.Text = ""  
  23.         txtUserPwd.Text = ""  
  24. Me.Close()  
  25.      
  26. Class  

到此为止,一个简单的三层架构就算实现了,隐约感觉设计上有些地方不合理,但说不好存在于哪些地方,希望由此路过的大牛们,多多指教,另外,这里的数据访问层代码冗余较多,在后续的博客中,会通过编写一个sqlHelp来实现优化,还有UML包图也会在后续博客中天上。

最后说说我自己的感触。

针对架构:用到了两个抽象工厂,刚开始是将两个工厂写到了一层中,这层的名称就叫Factory,而且封装注入的类也一并放到了这层中,但是在调试程序的时候出现DAL层依赖循环调用错误,不知道是代码还是设计上的原因?

针对接口设计:不太清楚业务逻辑层的方法设计是否合理,现在的一个问题就是如果用户名或者密码出错的话,并不是由UI层向用户提供反馈信息,而是由业务逻辑层担当了此任务,暂且就这么写吧,估计到后面写的多了就找到合适的方法了。

相关文章

迭代器模式(Iterator)迭代器模式(Iterator)[Cursor]意图...
高性能IO模型浅析服务器端编程经常需要构造高性能的IO模型,...
策略模式(Strategy)策略模式(Strategy)[Policy]意图:定...
访问者模式(Visitor)访问者模式(Visitor)意图:表示一个...
命令模式(Command)命令模式(Command)[Action/Transactio...
生成器模式(Builder)生成器模式(Builder)意图:将一个对...