分层思想的一个核心就是部件化,各个层之间是相互独立的,每一层可以随便抽取换成一个其他语言的版本,但只要与相应的接口吻合就行。
我用的三层架构大致是这样的,基本的三层就不说了,然后分别为业务逻辑层和数据访问层定义一个接口,由具体的那个层来实现,问题产生了,由谁来指定程序使用哪个具体的对象来实现相应接口?
为解决这个问题,我应用的是抽象工厂模式。分别为业务逻辑层和数据访问层添加一个抽象工厂。具体架构还是看下图吧。

这里的Utility是一个工具类,在下文中会提到。
学过设计模式的人都应该听过反射技术,但是一个系统中用到的类很多,需要对每一个类进行实例化,如果仅利用抽象工厂+反射模式,重复的代码比较多,如果哪一天整个DAL层发生变更,那么就要在代码中修改每一个用到的地方,不仅不容易维护,而且还很容易出错,未解决这个问题,对程序作了一个优化——用到依赖注入。还是看看代码吧。
1、先看看依赖注入的容器:这里我把这个注入容器放到了工具类中,刚开始学习设计模式,不知道是否合理,欢迎高手们指点。
- Imports?System.Configuration??
- Imports?System.Reflection??
- Public?Class?DependencyInjector??
- ??
- ??????
- ??????
- '''?</summary>??
- '''?<param?name="className">传入数据访问层中要实例化的类的名称</param>??
- '''?<returns>指定的数据访问层的类</returns>??
- '''?<remarks></remarks>??
- ????Function?GetDALObject(ByVal?className?As?String)?Object??
- ????????Dim?dal?Object??
- ????????Dim?dalName?String??
- Dim?fullClassName?String??
- Dim?dalObj? ??????????
- ????????dal?=?System.Configuration.ConfigurationManager.AppSettings("DAL")??
- ??
- ??????????
- ????????dalName?=?dal.ToString??
- '命名空间+类的名称,指明要实例化的类的路径??
- ????????fullClassName?=?dalName?+?"."?+?className??
- '通过反射,取得数据访问层对象??
- ????????dalObj?=?Assembly.Load(dalName).CreateInstance(fullClassName)??
- '返回指定的对象??
- Return?dalObj??
- ????End?Function??
- '''取得指定业务逻辑层的指定类??
- '''?<param?name="className">要应用的业务逻辑层的具体类的名称</param>??
- '''?<returns>指定的业务逻辑层的类(对象)</returns>??
- Function?GetBLLObject(Dim?bll?Dim?bllName?Dim?bllObj?'从配置文件中读取业务逻辑名称??
- ????????bll?=?System.Configuration.ConfigurationManager.AppSettings("BLL")??
- ????????bllName?=?bll.ToString??
- ????????fullClassName?=?bllName?+?"."?+?className??
- '利用反射取得业务逻辑层对象??
- ????????bllObj?=?Assembly.Load(bllName).CreateInstance(fullClassName)??
- Return?bllObj??
- Function??
- Class??
2、相关配置文件:
<appSettings>??
- ????add?key="connStr"??value="Persist?Security?Info=true;Data?Source=*****;Initial?Catalog=Charge_Sys_SelfDesign;User?ID=sa;PWD=****;"?/>??
- ????add?key="DAL"??value="DAL"?/>??
- add?key="BLL"??value="BLL"?/>??????
- ??</>??
3、业务逻辑层工厂:这里以在工厂中生产一个UserBLL类为例,在工厂中添加CreateUserBLL()方法,理论上讲,业务逻辑层有多少个类在此工厂中就要有多少个相应的方法,但是针对不同语言写的或者是不同的程序员用同一种语言写的同一层的代码(但都实现了程序指定的接口),我们在给类起名字的时候,只要用相同的类名就可以通过仅修改配置文件,达到换层的目的,而无需在工厂中改动任何代码。比如说,我现在要把DAL层换成AccessDAL,那么仅需要做如下修改即可。
add?key="DAL"??value="AccessDAL"?/>??
这就是分层的好处,当然也是面向对象思想的优势了。
上面主要是解释了一下配置文件,来看看业务逻辑工厂代码:
Imports?IBLL??
- Imports?Utility??
- Imports?System.Windows.Forms??
- Class?CreateBLL??
- Private?dependecy?New?Utility.DependencyInjector??
- '''?<summary>??
- '''?生成用户业务逻辑层访问对象??
- '''?</summary>??
- '''?<returns></returns>??
- Function?CreateUserBLL()?As?IBLL.IUserBLL??
- Try??
- ??????????????
- ??????????????
- '优化后只需指明具体类名,如果要换层,只需到配置文件中做简单修改即可,程序代码清晰,不宜出错。??
- ????????????Return?CType(dependecy.GetBLLObject("UserBLL"),?IBLL.IUserBLL)??
- Catch?ex?As?Exception??
- ????????????MsgBox(ex.Message)??
- ????????????Nothing??
- Class??
4、数据访问层工厂代码类似:
Imports?Utility??
- Imports?System.Configuration??
- Imports?System.Reflection??
- Imports?IDAL??
- Class?CreateDAL??
- Private?dependency?New?Utility.DependencyInjector??
- '''?生成用户指定的数据访问层对象??
- '''?<returns></returns>??
- '''?<remarks></remarks>??
- Function?CreateUserDAL()?As?IDAL.IUserDAL??
- Try??
- 'Return?CType(Assembly.Load("DAL").CreateInstance("DAL.UserDAL"),?IDAL.IUserDAL)??
- CType(dependency.GetDALObject("UserDAL"),?IDAL.IUserDAL)??
- As?Exception??
- ????????????MessageBox.Show(ex.Message)??
- Nothing??
- Class??
5、业务逻辑层接口代码比较简单,只需要定义一些相关接口方法即可,但是这里面体现了系统的架构,看似代码简单,实则反应程序员的架构水平。
Interface?IUserBLL??
- '返回用户登录的身份级别,在接口的实现中要验证用户名和密码??
- Function?LogIn(ByVal?modelUser?As?Model.User)?Interface??
6、数据访问层接口:
Interface?IUserDAL??
- '获取用户ID??
- Function?GetID('获取用户密码??
- Function?GetPwd('获取用户级别??
- Function?GetLevel(Interface??
7、业务逻辑层:
Imports?DALFactory??
- Imports?IBLL??
- Imports?IDAL??
- Imports?Model??
- Imports?System.Data.SqlClient??
- Imports?System.Collections.Generic??
- Class?UserBLL??
- Implements?IBLL.IUserBLL??
- Private?dalFactory?New?DALFactory.CreateDAL??
- '''?先判断用户名和密码是否正确,然后返回用户级别??
- '''?<param?name="modelUser">用户实体类</param>??
- '''?<returns>用户级别</returns>??
- String?Implements?IBLL.IUserBLL.LogIn??
- Dim?userLevel?If?dalFactory.CreateUserDAL.GetID(modelUser)?=?""?Then??
- ????????????????MsgBox("用户名错误!")??
- ???????????????? ????????????????Exit?If??
- If?dalFactory.CreateUserDAL.GetPwd(modelUser)?=?""?Then??
- ????????????????MsgBox("密码名错误!")??
- If??
- '通过数据访问层工厂指定实现数据访问层接口的具体的数据访问层以及该层的具体类??
- ????????????userLevel?=?dalFactory.CreateUserDAL.GetLevel(modelUser)??
- Return?userLevel??
- Class??
?
8、数据访问层:
Class?UserDAL??
- '实现数据访问层的接口??
- Implements?IDAL.IUserDAL??
- Private?sqlHelp?New?Utility.SQLServerDALHelp??
- '''?读取用户ID??
- '''?<param?name="modelUser">用户实体类</param>??
- '''?<returns>用户ID</returns>??
- Implements?IDAL.IUserDAL.GetID??
- Dim?User_ID?Dim?conn?New?SqlConnection(sqlHelp.connStr)??
- Dim?spName? ????????spName?=?"proc_GetUserID"??
- Dim?cmd?New?SqlCommand(spName,?conn)??
- ????????cmd.CommandType?=?CommandType.StoredProcedure??
- Dim?Param?As?SqlParameter??
- ????????Param?=?New?SqlParameter("@User_ID",?SqlDbType.VarChar)??
- ????????Param.Value?=?modelUser.User_ID??
- ????????cmd.Parameters.Add(Param)??
- ????????????conn.Open()??
- ????????????User_ID?=?cmd.ExecuteScalar.ToString??
- Return?User_ID??
- Throw?New?Exception(ex.Message)??
- Finally??
- ????????????conn.Close()??
- ????????????cmd.Dispose()??
- ????????????cmd?=?Return?User_ID??
- '''?读取用户密码??
- '''?<returns>密码</returns>??
- Implements?IDAL.IUserDAL.GetPwd??
- Dim?user_Pwd? ????????spName?=?"proc_GetUserPwd"??
- As?SqlParameter??
- ????????Param?=?New?SqlParameter("@User_Pwd",?SqlDbType.VarChar)??
- ????????Param.Value?=?modelUser.User_Pwd??
- ????????cmd.Parameters.Add(Param)??
- ????????????user_Pwd?=?cmd.ExecuteScalar.ToString??
- ????????????MsgBox(ex.Message)??
- New?Exception(ex.Message)??
- Finally??
- ????????????conn.Close()??
- ????????????cmd.Dispose()??
- ????????????cmd?=?Return?user_Pwd??
- '''?读取用户身份级别??
- '''?<returns>身份级别</returns>??
- Implements?IDAL.IUserDAL.GetLevel??
- Dim?User_Level? ????????spName?=?"proc_GetUserLevel"??
- New?SqlConnection(sqlHelp.connStr)??
- ????????cmd.CommandType?=?CommandType.StoredProcedure??
- ????????????conn.Open()??
- ????????????User_Level?=?cmd.ExecuteScalar.ToString??
- Return?User_Level??
- Class??
9、总算到UI层了:注意这里还没有添加针对用户输入合法性的验证,如,用户名、密码输入是否为空,是否符合指定格式等。
Imports?Model??
- Imports?System.Collections.Generic??
- Imports?BLLFactory??
- Class?frmLogIn??
- Private?bllFactory?New?BLLFactory.CreateBLL??
- Private?Sub?btnLogIn_Click(ByVal?sender?As?System.Object,?ByVal?e?As?System.EventArgs)?Handles?btnLogIn.Click??
- Dim?modelUser?New?Model.User??
- ????????modelUser.User_ID?=?txtUserID.Text??
- ????????modelUser.User_Pwd?=?txtUserPwd.Text??
- '通过业务逻辑工厂指定业务逻辑接口要使用的具体的业务逻辑层中的具体类??
- ????????userLevel?=?bllFactory.CreateUserBLL.LogIn(modelUser)??
- Select?Case?userLevel??
- Case?"一般用户"??
- Case?"操作员"??
- Case?"管理员"??
- ????????????????frmMDIParentForm.Show()??
- ??????????????
- Case?Else??
- ????????????????MsgBox("未知错误!")??
- Sub??
- Select??
- Sub?btnExit_Click(Handles?btnExit.Click??
- ????????txtUserID.Text?=?""??
- ????????txtUserPwd.Text?=?""??
- Me.Close()??
- ?????
- Class??
到此为止,一个简单的三层架构就算实现了,隐约感觉设计上有些地方不合理,但说不好存在于哪些地方,希望由此路过的大牛们,多多指教,另外,这里的数据访问层代码冗余较多,在后续的博客中,会通过编写一个SQLHelp来实现优化,还有UML包图也会在后续博客中天上。
最后说说我自己的感触。
针对架构:用到了两个抽象工厂,刚开始是将两个工厂写到了一层中,这层的名称就叫Factory,而且封装注入的类也一并放到了这层中,但是在调试程序的时候出现DAL层依赖循环调用错误,不知道是代码还是设计上的原因?
针对接口设计:不太清楚业务逻辑层的方法设计是否合理,现在的一个问题就是如果用户名或者密码出错的话,并不是由UI层向用户提供反馈信息,而是由业务逻辑层担当了此任务,暂且就这么写吧,估计到后面写的多了就找到合适的方法了。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|