因为之前用的纯三层的思想,所有的业务逻辑均在三层之间来回传递,包括U层的获得控件属性、传递参数到B层和接收从B层返回的数据,B层对业务逻辑的处理和D层操作数据库,在代码实现的过程中越来越觉得复杂和繁琐,最明显的地方就是数据库方面对数据的查询操作和非查询操作都有好多重复的地方,所有东西归纳总结一下无非就是查询和非查询,带参和不带参的查询,返回实体和返回datatable的区别,运用设计模式,封装这些有限的变化就非用到SqlHelper类了。然而对于方便系统要更换数据库和增加更多的功能所面临的问题,抽象工厂就是比较好的解决方案,我们用反射,仅仅修改一个字符串就能实现百分百的更换数据库,一个反射结合配置文件又最大限度的避免了抽象工厂运用过程中的不方便。
下面就是一个登录的简单实例,代码和详细的解释一定能让你看就懂!
包图:
![](https://img-blog.csdn.net/20140528092716531?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveGg5MjE=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
很简单的系统登录界面。
UI层:U层负责数据的输入与输出,首先对输入的数据要求进行判定,符合要求之后调用外观的验证用户和密码的方法。调用方法和纯三层的基本一样。
'添加引用
Imports BLL
Imports Entity
Public Class FrmLogin
Private Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click
'定义一个实体获得属性
Dim enUser As New Entity.UserEntity
enUser.struserID = txtUserID.Text.Trim()
enUser.strpwd = txtPWD.Text.Trim()
'判断是否输入了用户名及密码
If txtUserID.Text.Trim() = "" Then
MessageBox.Show("请输入用户ID", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
txtUserID.Select()
txtUserID.Focus()
Exit Sub
ElseIf IsNumeric(txtUserID.Text) = False Then
MsgBox("用户ID请输入数字!")
txtUserID.Text = ""
txtUserID.Select()
txtUserID.Focus()
Exit Sub
ElseIf txtPWD.Text.Trim() = "" Then
MessageBox.Show("请输入用密码", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
txtPWD.Select()
txtPWD.Focus()
Exit Sub
End If
Try
'定义一个外观层的对象
Dim FacadeLogin As New Facade.LoginFacade
Dim strResult1 As Boolean
strResult1 = FacadeLogin.CheckUser(enUser)
If strResult1 = False Then
MsgBox("用户不存在")
txtUserID.Text = ""
txtUserID.Select()
txtUserID.Focus()
End If
Dim table As DataTable
table = FacadeLogin.CheckPwd(enUser)
If Trim(txtPWD.Text) = Trim(table.Rows(0).Item(2)) Then
MsgBox("登录成功")
Else
MsgBox("密码不正确")
txtPWD.Text = ""
txtPWD.Select()
txtPWD.Focus()
End If
Catch ex As Exception
MsgBox("密码不正确")
txtPWD.Text = ""
txtPWD.Select()
txtPWD.Focus()
End Try
End Sub
Private Sub FrmLogin_Load(sender As Object, e As EventArgs) Handles MyBase.Load
txtUserID.Select()
txtUserID.Focus()
End Sub
End Class
外观层设计:外观层的两个方法真正实现的功能也类似于只是传参数而已,对于登录,外观层的作用不大,同样调用B层的方法
Public Class LoginFacade
Public Function CheckUser(ByVal enUser As Entity.UserEntity) As Boolean
'用于检查用户是否存在
Dim IsUserExistsBLL As New BLL.IsExists
Dim flag As Boolean
flag = IsUserExistsBLL.IsUserExists(enUser)
If flag = True Then
Return True
Else
Return False
End If
End Function
'检查密码是否正确
Public Function CheckPwd(ByVal enUser As Entity.UserEntity) As DataTable
Dim IsPwdBLL As New BLL.IsExists
Dim table As DataTable
table = IsPwdBLL.CheckUserPWD(enUser)
Return table
End Function
Imports IDAL
Imports Entity
Public Class IsExists
'检查用户是否存在
Public Function IsUserExists(ByVal enUser As Entity.UserEntity) As Boolean
'定义并实例化一个工厂,
Dim factory As New Factory.DataAccess
'定义一个接口变量
Dim IUser As IUser
'通过调用创建用户的工厂方法
IUser = factory.CreateUser
Dim table As DataTable
Dim flag As Boolean
table = IUser.CheckExistsUser(enUser)
If table.Rows.Count = 0 Then
flag = False
Else
flag = True
End If
Return flag
End Function
'检查密码是否正确
Public Function CheckUserPWD(ByVal enUser As UserEntity) As DataTable
Dim factory As New Factory.DataAccess '定义工厂并实例化工厂变量factory
Dim IUser As IUser '定义接口变量IUser
Dim table As DataTable '中间变量,用于存储D层查询到的数据
IUser = factory.CreateUser '调用工厂的CreateUser方法创建iuser接口实例
table = IUser.CheckExistsUser(enUser) '调用接口的方法UserIsExist,并将返回值返回给flag
Return table
End Function
End Class
工厂层设计:最经典的抽象工厂层的设计,用反射+配置文件,配置文件的讲解在上篇博客中详细介绍了,登录中应用的配置文件如下:
Imports System.Configuration
Imports IDAL
Public Class DataAccess
Private Shared ReadOnly AssemblyName As String = "DAL" '定义程序集名称变量,D层的命名空间的名字
Private Shared ReadOnly db As String = ConfigurationManager.AppSettings("DB") '将数据库类型通过文件形式读取到变量db中
'创建用户的工厂
Public Function CreateUser() As IUser
Dim className As String = AssemblyName + "." + db + "User"
Dim iuser As IUser
iuser = CType(Assembly.Load(AssemblyName).CreateInstance(className), IUser)
Return iuser
End Function
End Class
IDAL层设计:定义一个接口用于对B层和D层之间解耦。
Public Interface IUser
'检查用户是否存在和密码是否正确
Function CheckExistsUser(ByVal enUser As Entity.UserEntity) As DataTable
End Interface
DAL层设计:连接数据库,如果查询带有参数,应该注意参数的添加形式,这里很容易出错,查询语句的写法,在数据库的学习中都应用到了,各种查询我的博客中也介绍过,这里就不详细说了。
Imports Entity
Imports IDAL
Imports System.Data.SqlClient
Public Class SqlserverUser : Implements IUser
'查询用户和密码是否存在和正确
Public Function SelectExistsUser(ByVal enUser As Entity.UserEntity) As DataTable Implements IUser.CheckExistsUser
Dim sql As String '定义字符串变量sql,用于存放要执行的sql语句
Dim table As DataTable '定义表变量table,用于存储执行的结果并返回
Dim paras As SqlParameter() = {New SqlParameter("@userID", enUser.struserID),
New SqlParameter("@pwd", enUser.strpwd)} '定义参数变量paras,用于命令执行时传递参数
sql = "select * from T_UserInfo where userID=@userID and pwd=@pwd" '存储sql语句
table = SqlHelper.SqlHelper.GetDataTable(sql, CommandType.Text, paras) '执行sql语句,将结果赋给table
Return table
'SelectExistsUser = table '返回表table
End Function
'添加的引用
Imports System.Data.SqlClient
Imports System.Configuration
Public Class SqlHelper
Private Shared ConnectionString As String = ConfigurationManager.AppSettings("strConn")
''' <summary>
''' 执行带参数的查询操作
''' </summary>
''' <param name="cmdTxt">参数cmdTxt为所要执行的sql语句</param>
''' <param name="cmdType">查询时的查询方式</param>
''' <param name="paras">查询时的命令参数paras</param>
''' <returns>查询后以表的方式返回,如下面的adataset.Tables(0)</returns>
''' <remarks></remarks>
Public Shared Function GetDataTable(ByVal cmdTxt As String, ByVal cmdType As CommandType, ByVal paras As SqlParameter()) As DataTable
Dim conn As SqlConnection = New SqlConnection(ConnectionString) '建立数据库连接
Dim cmd As SqlCommand '定义命令变量cmd
Dim adaptor As SqlDataAdapter '定义数据适配器
Dim adataset As DataSet '定义并实例化数据缓冲区对象
cmd = New SqlCommand(cmdTxt, conn) '在conn上面执行实例化命令变量,并执行语句cmdType
cmd.CommandType = cmdType '命令执行的类型
cmd.Parameters.AddRange(paras) '命令执行时的参数
adaptor = New SqlDataAdapter(cmd) '将结果绑定到数据适配器变量adaptor上面
adataset = New DataSet
Try
'如果数据库连接状态为关闭则将其打开
If conn.State = ConnectionState.Closed Then
conn.Open()
End If
adaptor.Fill(adataset) '向adaptor对象中填充查询的数据
Catch ex As Exception
'错误处理程序,出错则提示
MsgBox(ex.Message, , "数据库操作")
Finally
'如果连接状态为打开则将其关闭,释放内存
If conn.State = ConnectionState.Open Then
conn.Close()
End If
End Try
'以表格形式返回结果
Return adataset.Tables(0)
End Function
End Class
实体层设计:上面的每一层都要应用到实体层,可以说不管是三层还是多层,数据的传递都是依赖实体层进行的,这一层必不可少。实体层的设计离不开数据库表的设计,基本上每一个实体都是一张表的化身。可以看出表中的字段就是实体的属性,提醒一下,像获得自己的计算机名称这种变量可以定义为全局变量,我们可以把这种全局变量放到类模块中,这样整个系统都可以使用全局变量了。
Public Class UserEntity
Private userID As String
Private userName As String
Private level As String
Private pwd As String
Public Property struserID() As String
Get
Return userID
End Get
Set(value As String)
userID = value
End Set
End Property
Public Property struserName() As String
Get
Return userName
End Get
Set(value As String)
userName = value
End Set
End Property
Public Property strlevel() As String
Get
Return level
End Get
Set(value As String)
level = value
End Set
End Property
Public Property strpwd() As String
Get
Return pwd
End Get
Set(value As String)
pwd = value
End Set
End Property
下面就是一个登录的简单实例,代码和详细的解释一定能让你看就懂!
包图:
(其实外观在系统登录的时候没有什么大的作用,我在设计整个机房架构的过程中选择了使用外观是为了方便以后的实现过程。)
总的思想仍然是三层,在这基础上增加的另外抽象工厂、外观、SqlHelper和IDAL数据库接口层。也只是把在纯三层中的代码分类整理分布到各个层中,增加接口层的主要作用大家也都清楚,我就不详细介绍了。每个层仍然只是干自己的事,尽量的符合每个类的单一职责原则。
很简单的系统登录界面。
UI层:U层负责数据的输入与输出,首先对输入的数据要求进行判定,符合要求之后调用外观的验证用户和密码的方法。调用方法和纯三层的基本一样。
'添加引用
Imports BLL
Imports Entity
Public Class FrmLogin
Private Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click
'定义一个实体获得属性
Dim enUser As New Entity.UserEntity
enUser.struserID = txtUserID.Text.Trim()
enUser.strpwd = txtPWD.Text.Trim()
'判断是否输入了用户名及密码
If txtUserID.Text.Trim() = "" Then
MessageBox.Show("请输入用户ID", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
txtUserID.Select()
txtUserID.Focus()
Exit Sub
ElseIf IsNumeric(txtUserID.Text) = False Then
MsgBox("用户ID请输入数字!")
txtUserID.Text = ""
txtUserID.Select()
txtUserID.Focus()
Exit Sub
ElseIf txtPWD.Text.Trim() = "" Then
MessageBox.Show("请输入用密码", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
txtPWD.Select()
txtPWD.Focus()
Exit Sub
End If
Try
'定义一个外观层的对象
Dim FacadeLogin As New Facade.LoginFacade
Dim strResult1 As Boolean
strResult1 = FacadeLogin.CheckUser(enUser)
If strResult1 = False Then
MsgBox("用户不存在")
txtUserID.Text = ""
txtUserID.Select()
txtUserID.Focus()
End If
Dim table As DataTable
table = FacadeLogin.CheckPwd(enUser)
If Trim(txtPWD.Text) = Trim(table.Rows(0).Item(2)) Then
MsgBox("登录成功")
Else
MsgBox("密码不正确")
txtPWD.Text = ""
txtPWD.Select()
txtPWD.Focus()
End If
Catch ex As Exception
MsgBox("密码不正确")
txtPWD.Text = ""
txtPWD.Select()
txtPWD.Focus()
End Try
End Sub
Private Sub FrmLogin_Load(sender As Object, e As EventArgs) Handles MyBase.Load
txtUserID.Select()
txtUserID.Focus()
End Sub
End Class
外观层设计:外观层的两个方法真正实现的功能也类似于只是传参数而已,对于登录,外观层的作用不大,同样调用B层的方法
Public Class LoginFacade
Public Function CheckUser(ByVal enUser As Entity.UserEntity) As Boolean
'用于检查用户是否存在
Dim IsUserExistsBLL As New BLL.IsExists
Dim flag As Boolean
flag = IsUserExistsBLL.IsUserExists(enUser)
If flag = True Then
Return True
Else
Return False
End If
End Function
'检查密码是否正确
Public Function CheckPwd(ByVal enUser As Entity.UserEntity) As DataTable
Dim IsPwdBLL As New BLL.IsExists
Dim table As DataTable
table = IsPwdBLL.CheckUserPWD(enUser)
Return table
End Function
End Class
Imports IDAL
Imports Entity
Public Class IsExists
'检查用户是否存在
Public Function IsUserExists(ByVal enUser As Entity.UserEntity) As Boolean
'定义并实例化一个工厂,
Dim factory As New Factory.DataAccess
'定义一个接口变量
Dim IUser As IUser
'通过调用创建用户的工厂方法
IUser = factory.CreateUser
Dim table As DataTable
Dim flag As Boolean
table = IUser.CheckExistsUser(enUser)
If table.Rows.Count = 0 Then
flag = False
Else
flag = True
End If
Return flag
End Function
'检查密码是否正确
Public Function CheckUserPWD(ByVal enUser As UserEntity) As DataTable
Dim factory As New Factory.DataAccess '定义工厂并实例化工厂变量factory
Dim IUser As IUser '定义接口变量IUser
Dim table As DataTable '中间变量,用于存储D层查询到的数据
IUser = factory.CreateUser '调用工厂的CreateUser方法创建iuser接口实例
table = IUser.CheckExistsUser(enUser) '调用接口的方法UserIsExist,并将返回值返回给flag
Return table
End Function
End Class
工厂层设计:最经典的抽象工厂层的设计,用反射+配置文件,配置文件的讲解在上篇博客中详细介绍了,登录中应用的配置文件如下:
反射技术的核心就是对于如何应用字符串修改引起的变化,在反射的格式中涉及到的什么是程序集的名称,什么是命名空间,什么是类名,大家要清楚,不要搞混了,代码中对于这几个的写法要注意。
Imports System.Configuration
Imports IDAL
Public Class DataAccess
Private Shared ReadOnly AssemblyName As String = "DAL" '定义程序集名称变量,D层的命名空间的名字
Private Shared ReadOnly db As String = ConfigurationManager.AppSettings("DB") '将数据库类型通过文件形式读取到变量db中
'创建用户的工厂
Public Function CreateUser() As IUser
Dim className As String = AssemblyName + "." + db + "User"
Dim iuser As IUser
iuser = CType(Assembly.Load(AssemblyName).CreateInstance(className), IUser)
Return iuser
End Function
End Class
IDAL层设计:定义一个接口用于对B层和D层之间解耦。
Public Interface IUser
'检查用户是否存在和密码是否正确
Function CheckExistsUser(ByVal enUser As Entity.UserEntity) As DataTable
End Interface
DAL层设计:连接数据库,如果查询带有参数,应该注意参数的添加形式,这里很容易出错,查询语句的写法,在数据库的学习中都应用到了,各种查询我的博客中也介绍过,这里就不详细说了。
Imports Entity
Imports IDAL
Imports System.Data.SqlClient
Public Class SqlserverUser : Implements IUser
'查询用户和密码是否存在和正确
Public Function SelectExistsUser(ByVal enUser As Entity.UserEntity) As DataTable Implements IUser.CheckExistsUser
Dim sql As String '定义字符串变量sql,用于存放要执行的sql语句
Dim table As DataTable '定义表变量table,用于存储执行的结果并返回
Dim paras As SqlParameter() = {New SqlParameter("@userID", enUser.struserID),
New SqlParameter("@pwd", enUser.strpwd)} '定义参数变量paras,用于命令执行时传递参数
sql = "select * from T_UserInfo where userID=@userID and pwd=@pwd" '存储sql语句
table = SqlHelper.SqlHelper.GetDataTable(sql, CommandType.Text, paras) '执行sql语句,将结果赋给table
Return table
'SelectExistsUser = table '返回表table
End Function
End Class
'添加的引用
Imports System.Data.SqlClient
Imports System.Configuration
Public Class SqlHelper
Private Shared ConnectionString As String = ConfigurationManager.AppSettings("strConn")
''' <summary>
''' 执行带参数的查询操作
''' </summary>
''' <param name="cmdTxt">参数cmdTxt为所要执行的sql语句</param>
''' <param name="cmdType">查询时的查询方式</param>
''' <param name="paras">查询时的命令参数paras</param>
''' <returns>查询后以表的方式返回,如下面的adataset.Tables(0)</returns>
''' <remarks></remarks>
Public Shared Function GetDataTable(ByVal cmdTxt As String, ByVal cmdType As CommandType, ByVal paras As SqlParameter()) As DataTable
Dim conn As SqlConnection = New SqlConnection(ConnectionString) '建立数据库连接
Dim cmd As SqlCommand '定义命令变量cmd
Dim adaptor As SqlDataAdapter '定义数据适配器
Dim adataset As DataSet '定义并实例化数据缓冲区对象
cmd = New SqlCommand(cmdTxt, conn) '在conn上面执行实例化命令变量,并执行语句cmdType
cmd.CommandType = cmdType '命令执行的类型
cmd.Parameters.AddRange(paras) '命令执行时的参数
adaptor = New SqlDataAdapter(cmd) '将结果绑定到数据适配器变量adaptor上面
adataset = New DataSet
Try
'如果数据库连接状态为关闭则将其打开
If conn.State = ConnectionState.Closed Then
conn.Open()
End If
adaptor.Fill(adataset) '向adaptor对象中填充查询的数据
Catch ex As Exception
'错误处理程序,出错则提示
MsgBox(ex.Message, , "数据库操作")
Finally
'如果连接状态为打开则将其关闭,释放内存
If conn.State = ConnectionState.Open Then
conn.Close()
End If
End Try
'以表格形式返回结果
Return adataset.Tables(0)
End Function
End Class
实体层设计:上面的每一层都要应用到实体层,可以说不管是三层还是多层,数据的传递都是依赖实体层进行的,这一层必不可少。实体层的设计离不开数据库表的设计,基本上每一个实体都是一张表的化身。可以看出表中的字段就是实体的属性,提醒一下,像获得自己的计算机名称这种变量可以定义为全局变量,我们可以把这种全局变量放到类模块中,这样整个系统都可以使用全局变量了。
Public Class UserEntity
Private userID As String
Private userName As String
Private level As String
Private pwd As String
Public Property struserID() As String
Get
Return userID
End Get
Set(value As String)
userID = value
End Set
End Property
Public Property struserName() As String
Get
Return userName
End Get
Set(value As String)
userName = value
End Set
End Property
Public Property strlevel() As String
Get
Return level
End Get
Set(value As String)
level = value
End Set
End Property
Public Property strpwd() As String
Get
Return pwd
End Get
Set(value As String)
pwd = value
End Set
End Property
End Class