起初,机房收费系统用纯三层在做,三层结构已把高内聚,低耦合的思想发挥的淋漓尽致。因为一直听说“抽象工厂”,也开始尝试去应用,经过了一番尝试,也OK了。其中当然会用到反射和配置文件,而接口的引入也是顺理成章的。接口对B层和D层又完成了进一步解耦。
有了图也就类似于有了思想,有了宏观的把控。可以看到,这个包图,是从最经典的三层UI-BLL-DAL加入设计模式演化而来。之所以采用抽象工厂模式是考虑到更换数据库的方便。(而应用外观模式,是为了解决UI层和BLL层耦合性过高的问题,UI层不必知道BLL层的存在,Facade(外观)知道BLL层的哪些类负责处理哪些请求,它将UI的请求代理给适当的BLL层的类。使外部调用更方便。)
下面就以登录(Login)这个小Demo中的部分功能为例来阐述过程的实现。
前面的一篇博客(机房收费系统总结之3——SqlHelper)介绍过,这里不再赘述。
实体层并不属于三层中的任何一层,它是独立出来的一层,可以把他看做自定义变量的组合,供三层使用。数据库设计好了,我们要根据数据库中的表抽象实体类,在机房收费系统中,实体类基本上是跟表一一对应的,一个表映射出一个实体类,表的字段即为实体类的属性。但,对于全局用的东西,我们可以建一个实体类,把该全局变量作为它的属性。所以,实体类的数量可能多余表的数量。
三、IDAL'/*************************************************************************************
'类 名 称:UserInfoEntity
'命名空间:Entity
'创建时间:2013-08-09 14:47:28
'作 者:张连海
'小 组:
'修改时间:
'修 改 人:
'版 本 号:v1.0.0
'*************************************************************************************/
Public Class UserInfoEntity
Private strUserName As String 'Hold the UserName property
Private strPassword As String 'Hold the Password property
Private strRealName As String 'Hold the RealName property
Private strGmlevel As String 'Hold the Gmlevel property
Private strAccountBy As String 'Hold the AccountBy property
'设置UserName属性
Public Property UserName() As String
Get
Return strUserName
End Get
Set(value As String)
strUserName = value
End Set
End Property
'设置Password属性
Public Property Password() As String
Get
Return strPassword
End Get
Set(value As String)
strPassword = value
End Set
End Property
'设置RealName属性
Public Property RealName() As String
Get
Return strRealName
End Get
Set(value As String)
strRealName = value
End Set
End Property
'设置Gmlever属性
Public Property Gmlevel() As String
Get
Return strGmlevel
End Get
Set(value As String)
strGmlevel = value
End Set
End Property
'设置AccountBy属性
Public Property AccountBy() As String
Get
Return strAccountBy
End Get
Set(value As String)
strAccountBy = value
End Set
End Property
End Class
这里加了一层接口,利用反射和抽象工厂,以防更换数据库。
Imports Entity
Public Interface IUserInfo
''' <summary>
''' 调用SqlHelper把传来的用户基本信息添加到T_UserInfo表中
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 添加 语句受影响的行数,为Integer类型</returns>
Function AddUserInfo(ByVal enUserInfo As UserInfoEntity) As Integer
Function DeleteUser(ByVal enUserInfo As UserInfoEntity) As Integer
''' <summary>
''' 调用SqlHelper完成对T_UserInfo表中相应用户的密码
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 更新 语句受影响的行数,为Integer类型</returns>
''' <remarks></remarks>
Function UpdatePassword(ByVal enUserInfo As UserInfoEntity) As Integer
''' <summary>
''' 调用SqlHelper来查询T_UserInfo表中相应用户信息
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 添加 语句受影响的行数,为Integer类型</returns>
Function SelectUserInfo(ByVal enUserInfo As UserInfoEntity) As DataTable
End Interface
四、D层——SqlServerDAL
这一层的主要任务是直接操作数据库,完成对数据的增删改查等。这里我们仍然根据数据表来抽象DAL层的类,基本上也是一个表对应一个类,这样当我们增加新的表,直接增加新的DAL层类就可以,很好地符合了“开闭原则”。接口中的方法,也就是需要通过D层来实现的方法,在调用的过程中,D层就会将这个方法实现,返回相应的值 。但注意的是:DAL层类的数量也可能多余表的数量,可以定义一个接口叫ITime,这个接口是用来获取服务器时间。五、接下来就是重头戏了——抽象工厂(Factory)Imports System.Data.SqlClient
Imports Entity
Imports IDAL
Public Class UserInfoDAL : Implements IDAL.IUserInfo
Private clsSqlHelper As SqlHelper.SqlHelper = New SqlHelper.SqlHelper() '声明并实例化SqlHelper类
''' <summary>
''' 调用SqlHelper把传来的用户基本信息添加到T_UserInfo表中
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 查询 得到的结果,为DataTable类型</returns>
Public Function AddUserInfo(ByVal enUserInfo As UserInfoEntity) As Integer Implements IDAL.IUserInfo.AddUserInfo
'声明并实例化需要执行的SQL语句
Dim strSql As String = "insert into T_UserInfo (userName,password,realName,gmlevel,accountBy) values (@userName,@password,@realName,@gmlevel,@accountBy)"
'声明并实例化参数数组
Dim sqlParams As SqlParameter() = {New SqlParameter("@userName", enUserInfo.UserName),
New SqlParameter("@password", enUserInfo.Password),
New SqlParameter("@realName", enUserInfo.RealName),
New SqlParameter("@gmlevel", enUserInfo.Gmlevel),
New SqlParameter("@accountBy", enUserInfo.AccountBy)}
'调用SqlHelper类中的ExecAddDelUpdate()方法来执行添加信息,获取返回值并Return
Return clsSqlHelper.ExecAddDelUpdate(strSql, CommandType.Text, sqlParams)
End Function
''' <summary>
''' 删除用户信息
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 删除 语句受影响的行数,为Integer类型</returns>
''' <remarks></remarks>
Public Function DeleteUser(ByVal enUserInfo As UserInfoEntity) As Integer Implements IUserInfo.DeleteUser
'声明并实例化需要执行的SQL语句
Dim strSql As String = "delete T_UserInfo where userName =@userName"
'声明并实例化参数数组
Dim sqlParams As SqlParameter() = {New SqlParameter("@userName", enUserInfo.UserName)}
'调用SqlHelper类中的ExecAddDelUpdate()方法来执行添加信息,获取返回值并Return
Return clsSqlHelper.ExecAddDelUpdate(strSql, CommandType.Text, sqlParams)
End Function
''' <summary>
''' 调用SqlHelper完成对T_UserInfo表中相就用户密码的悠
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 更新 语句受影响的行数,为Integer类型</returns>
''' <remarks></remarks>
Public Function UpdatePassword(ByVal enUserInfo As UserInfoEntity) As Integer Implements IUserInfo.UpdatePassword
'声明并实例化需要执行的SQL语句
Dim strSql As String = "update T_UserInfo set password =@password where userName =@userName"
'声明并实例化参数数组
Dim sqlParams As SqlParameter() = {New SqlParameter("@userName", enUserInfo.UserName),
New SqlParameter("@password", enUserInfo.Password)}
'调用SqlHelper类中的ExecAddDelUpdate()方法来执行添加信息,获取返回值并Return
Return clsSqlHelper.ExecAddDelUpdate(strSql, CommandType.Text, sqlParams)
End Function
''' <summary>
''' 根据用户名调用SqlHelper来查询T_UserInfo表中相应用户信息
''' </summary>
''' <param name="enUserInfo">用户信息实体类</param>
''' <returns>返回执行 查询 得到的结果,为DataTable类型</returns>
''' <remarks></remarks>
Public Function SelectUserInfo(ByVal enUserInfo As Entity.UserInfoEntity) As DataTable Implements IDAL.IUserInfo.SelectUserInfo
Dim strUserName As String = enUserInfo.UserName '声明并实例化strUserName为实体中的用户名
Dim table As DataTable '声明一个DataTable类型变量
Dim strSQL As String = "select * from T_UserInfo where userName=@userName" '声明并实例化需要执行的SQL语句
Dim sqlParams As SqlParameter() = {New SqlParameter("@userName", strUserName)} '声明并实例化参数数组
table = clsSqlHelper.ExecSelect(strSQL, CommandType.Text, sqlParams) '调用SqlHelper类中的ExecSelect()方法来执行查询,并获取返回值
Return table '返回查询结果
End Function
End Class
工厂层的主要作用是应用配置文件和反射技术实现数据库的更换功能。在Factory层中首先定义程序集的名字和明明空间的名字,将程序集的名字和命名空间的值写在配置文件中,当执行到Factory函数时程序会自动通过读取配置文件中的相应字符,按照路径实例化出相应的对象。
配置文件(之前的博客也提及了——机房收费系统总结之2——配置文件(初尝))内容如下:
<appSettings>
<add key="DB" value="SQLServer"/>
</appSettings>
抽象工厂中的代码如下:
Imports System.Configuration '添加对配置文件的引用
Imports System.Reflection '添加对反射的引用
Imports IDAL
Public Class DataAccess
Private strAssembly As String '程序集名
''' <summary>
''' 构造函数,通过调用配置文件为strAssembly赋初值
''' </summary>
Sub New()
Me.strAssembly = ConfigurationManager.AppSettings("DB") & "DAL"
End Sub
''' <summary>
''' 实例化接口为IUserInfo为UserInfoDAL
''' </summary>
''' <returns>IUserInfo</returns>
Public Function CreateUserInfoDAL() As IUserInfo
Dim strInstance As String = strAssembly & "." & "UserInfoDAL" '所要实例化的对象(程序集与命名空间同名)
Return CType(Assembly.Load(strAssembly).CreateInstance(strInstance), IUserInfo) '返回IUserInfo
End Function
End Class
在上面的代码中"DB"的值为使用的相应数据库的名字,在本程序中明明空间的名字和程序集的名字相同,所以没有灵性设置。通过将使用的数据库的名字用配置文件中key值来代替便可以十分方便的更换数据库。另外在上述代码中将实例化的D层类通过向上转型转换成接口类,然后通过调用接口类中的函数来调用D层中实现该接口的函数。
BLL层的类,我们可以根据功能来分,把与该功能相关的操作集成到一个BLL层的类里,这里我们要把握好粒度,平衡就好。尽量做到符合单一职责原则,一个类完成一个功能,即不要在BLL层出现类之间互相调用的情况,虽然可以减少代码量,但会增加系统的复杂性,造成模块与模块之间的强耦合。
下面仅以判断用记密码是否正确为例:
七、FaçadeImports Factory
Imports IDAL
Imports Entity
Public Class ConditionBLL
'声明并实例化factory为DataAccess类
Private ReadOnly factory As DataAccess = New DataAccess()
''' <summary>
''' 判断用户密码是否正确:根据返回来的DataTable中的Password的属性值,与传来的所输入和密码对比,看密码是否输入正确
''' </summary>
''' <param name="enUserinfo">用户信息实体类</param>
''' <returns>如果密码不正确,抛出异常</returns>
Public Function IsPswRight(ByVal enUserinfo As UserInfoEntity)
'声明并实例化变量interfaceUserInfo为:调用factory.CreateUserInfoDAL()方法所返回来的IUserInfo
Dim interfaceUserInfo As IUserInfo = factory.CreateUserInfoDAL()
'声明并实例化变量table为:通过传入的enUserInfo参数而调用interfaceUserInfo的SelectUserInfo(enUserInfo)方法所返回来的DataTable型数据
Dim table As DataTable = interfaceUserInfo.SelectUserInfo(enUserinfo)
'DataTable中的Password的属性值,与传来的所输入和密码对比,看密码是否输入正确,如果密码不正确,抛出异常
If table.Rows(0).Item("Password") <> enUserinfo.Password Then
Throw New Exception("密码输入不正确")
End If
End Function
End Class
简称F层,该层的作用可以形象的比喻成“纲”,即渔网中能够提携全网的绳子。以登录系统为例,当我们正常登录时,依次应该发生验证用户输入是否合法(U层中进行),查询用户是否存在,密码是否正确……,最后添加用户登录记录等功能。F层中的Login()方法提中则分别依次对这几个方法进行了调用,并且在依次调用时分别做了相应的意外处理。简单来讲就是调用Facade层中的这一个函数即实现登录功能
Imports Entity
Imports BLL
Public Class LoginFacade
Private bllIsExists As IsExistsBLL = New IsUserExistsBLL() '声明并实例化bllIsExists为IsUserExistsBLL类
Private bllCondition As ConditionBLL = New ConditionBLL() '声明并实例化bllCondition为ConditionBLL类
Private bllAddAboutUser As AddAboutUserBLL = New AddAboutUserBLL() '声明并实例化bllLogin为LoginBLL类
''' <summary>
''' 完成登录的一系列操作
''' </summary>
''' <param name="enUserInfo">用户信息实休类</param>
''' <param name="enUserWork">用户工作信息实体类</param>
Public Function Login(ByVal enUserInfo As UserInfoEntity, ByVal enUserWork As UserWorkEntity)
'判断用户是否存在
If bllIsExists.IsExists(enUserInfo) = 0 Then
Throw New Exception("此用户不存在")
End If
bllCondition.IsPswRight(enUserInfo) '判断输入密码是否正确
bllCondition.IsUserOn(enUserWork) '判断用户是否已登录系统
bllAddAboutUser.AddUserWorkOn(enUserWork) '添加用户登录信息
End Function
End Class
八、UI
U层负责数据的录入与输出,在U层,先通过调用U层中的验证用户类的验证方法来确认所输入用户是否合法,然后调用外观层中的方法进行判定,并完成登录。
Imports Facade
Imports Entity
Public Class frmLogin
Private Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click
Dim strComName As String = System.Net.Dns.GetHostName() '获取计算机名并赋值给变量strComName
'给公共变量中的计算机名和用户名赋值
PublicEntity.ComName = strComName
PublicEntity.UserName = txtUserName.Text.Trim()
'定义一个UserInfoEntity类型的实体类变量,并分别把输入的用户名和密码赋值给变量的UserName属性和Password属性
Dim enUserInfo As New UserInfoEntity With {.UserName = txtUserName.Text.Trim(), .Password = txtPassword.Text.Trim()}
'定义一个UserWorkEntity类型的实体类变量,并分别打输入的用户名和所获得的计算机名赋值给变量的UserName属性和ComName属性
Dim enUserWord As New UserWorkEntity With {.UserName = txtUserName.Text.Trim(), .ComName = strComName}
'判断是否输入了用户名及密码
If txtUserName.Text.Trim() = "" Then
MessageBox.Show("请输入用户名", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit Sub
ElseIf txtPassword.Text.Trim() = "" Then
MessageBox.Show("请输入用密码", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
Exit Sub
End If
Try
Dim facadeLogin As LoginFacade = New LoginFacade() '声明LoginFacade类型变量facadeLogin,并实例化
facadeLogin.Login(enUserInfo, enUserWord) '调用Login()方法,完成登录
Me.Hide() '隐藏登录窗体
frmMain.Show() '显示主窗体
Catch ex As Exception '捕获异常并显示
MsgBox(ex.Message, CType(vbOKOnly + MsgBoxStyle.Information, MsgBoxStyle), "提示")
End Try
End Sub
End Class