zl程序教程

您现在的位置是:首页 >  后端

当前栏目

【机房重构】一步一步往上爬——不仅仅是三层

重构 一步 三层 机房 不仅仅
2023-09-14 08:56:49 时间
 不知道大家还记不记得之前学习的UML中一个单独列出来的一种图,也就是这次我想说的包图。那个时候,让我们画机房收费系统的各种图,用例图、类图等等,通过自己反复琢磨,还都勉勉强强画出来了。唯独只有包图,我是一点东西也没有画上,只是见到了传说中的“包”是长什么样子的。

 现在到了机房重构的阶段了,之前也学习了三层,终于是知道了包图该怎么画了。但也不仅仅是理解了包图,而且也是见证到了包图是如何在一步一步层层升级的。下面就将用一个机房系统中的登录实例来看看为什么说《不仅仅是三层》。

 三层的学习在有了一个初识之后,欠缺的就是一个实践了。每一层与每一层的关系理清了,登录小实例也就很容易实现了。下面先看看仅仅是三层的一个登录包图:

 从图中可以很清楚的看到三个层之间的关系。用个形象的比喻去说,就是一场擂台赛,双方代表分别为U层和D层,其中裁判由B层担任。

 言归正传,下面描述一下这一登录实例中的一个时序:用户输入信息,通过实体将信息从U层传入B层,而另一过程,通过实体将信息从D层也传入B层。于是,开始在B层进行逻辑判断的过程,最后同样是通过实体将B层结果返回给U层,以回馈给用户。但这样的话,并不能很好地符合”低耦合“的编程思想,所以三层+Model(实体层)需要升级了。

 如何升级?这里,之前学习的设计模式就可以应用上了。外观模式与抽象工厂模式,在那段时间还不能很好地理解它们的作用,而现在机房重构了,对于它们的作用真的是有了一个不一样的深刻的体会:为了降低U层与B层之间的耦合,我们在两者之间加入了外观模式;为了降低D层与B层之间的耦合,我们在两者之间加入了工厂模式和接口层。

 下面就是一个全新的包图:


 看着这样一个图,自己是一次又一次的琢磨才把关系给理清楚的呀。理论上看起来确实很容易就理解了,但对于代码的编写,真的是不容易了。参数的传来传去,时不时的就会把自己给传进去出不来了。所以说,在自己多次糊涂与清醒之后,感觉最重要的还是要明确每一层应该做的,在此先用最简洁的语句来说说究竟每层起个什么样的作用:

 1.UI(用户界面层),一方面是将用户输入的信息传入给外观层,另一方面是将外观层的信息显示给用户;

 2.Facade(外观层),一方面是接收U层用户的信息传给B层进行后续操作,另一方面是接收B层的信息传给U层;

 3.BLL(逻辑判断层),一方面是接收外观层传来的信息,另一方面是接收通过工厂和接口后传来的D层的信息;

 4.Factory(工厂层),其中通过读取配置文件,在以后更换数据库的时候,只需要增加一个新的类,而不需要修改原来的代码;

 5.IDAL(接口层),其中就是对数据库进行增、删、改、查操作的封装,降低B层与D层的直接耦合;

 6.DAL(数据访问层),是通过实现接口的方法而去对用户的需求做相应地操作,一方面可能是需要查询数据库中的数据,另一方面则可能是为了增删改数据。而为了减少代码量,又用到了一个SQLHelper类。

 7.Model(实体层),数据的传输,既然需要避免传输者与接收者之间的耦合,那么总得需要一个中介,实体层便是那个中介,对封装好后的数据进行传输。

 在知道了每一层的作用,下面就是每一层该怎么去做。通过前些天的琢磨,一次次的单步调试,总算是对这个《不仅仅是三层》的登录实例有了进一步的认识。下面就通过一张时序图再次理理究竟各层之间是一个怎么样的关系才实现登录的:


 这里就不再描述了,图中画得肯定比我写下来的清楚的多。如果大家有什么意见或建议,也欢迎来与我一起交流交流。下面看看各个层中一些重要的代码:

SQLHelper类(这里用到的是有参数的查询操作):

 Public Function ExecSelect(ByVal cmdText As String, ByVal cmdType As CommandType, ByVal paras As SqlParameter()) As DataTable

 Dim sqlAdapter As SqlDataAdapter

 Dim dt As New DataTable

 Dim ds As New DataSet

 还是给cmd赋值 

 cmd.CommandText = cmdText

 cmd.CommandType = cmdType

 cmd.Connection = conn

 cmd.Parameters.AddRange(paras) 参数添加 

 sqlAdapter = New SqlDataAdapter(cmd) 实例化adapter 

 sqlAdapter.Fill(ds) 用adapter将dataSet填充 

 dt = ds.Tables(0) datatable为dataSet的第一个表 

 cmd.Parameters.Clear() 清除参数 

 Catch ex As Exception

 MsgBox("查询失败", CType(vbOKOnly + MsgBoxStyle.Exclamation, MsgBoxStyle), "警告")

 Finally 最后一定要销毁cmd 

 Call CloseCmd(cmd)

 End Try

 Return dt

 End Function
BLL层:

Public Class LoginBLL

 检查用户是否存在 

 Public Function IsUserExists(ByVal UserInfo As Model.EntityUserInfo) As Boolean

 Dim factory As New Factory.LoginFactory()

 Dim Iuser As IDAL.IUserinfo

 调用"创建用户"的工厂方法 

 Iuser = factory.CreateUserInfo()

 Dim table As DataTable

 Dim flag As Boolean

 table = Iuser.QueryUser(UserInfo)

 由于在sqlHelper中返回的形式为表格形式(adataset.Tables(0)),且开头第一列表示为0,所以Item(0)则代表用户名 

 If table.Rows.Count = 0 Then

 flag = False

 Else

 flag = True

 End If

 Return flag

 End Function

 查看密码是否正确 

 Public Function isPWDright(ByVal UserInfo As Model.EntityUserInfo) As DataTable

 Dim factory As New Factory.LoginFactory()

 Dim Iuser As IDAL.IUserinfo

 Dim table As DataTable 中间变量,用于存储D层查询到的数据 

 Iuser = factory.CreateUserInfo 调用工厂的CreateUserInfo方法创建Iuser接口实例 

 table = Iuser.QueryUser(UserInfo) 调用接口的方法. 

 Return table

 End Function

End Class
DAL层:

Public Class LoginDAL : Implements IDAL.IUserinfo

 实现接口中的方法。 

 Private sqlHelper As SQLHelper.SqlHelper = New SQLHelper.SqlHelper

 Public Function QueryUser(ByVal UserInfo As Model.EntityUserInfo) As DataTable Implements IUserinfo.QueryUser

 Dim Sql As String span 中间变量,用于储存从数据库中查找到的信息 /span 

 Dim table As DataTable 声明一个DataTable类型变量 

 Dim sqlParams As SqlParameter() = {New SqlParameter("@UserName", UserInfo.ID), New SqlParameter("@password", UserInfo.Password)} 声明并实例化参数数组 

 Sql = "select * from User_Info where UserID=@UserName and UserPassword=@password"

 下句为调用SqlHelper类中的GetDataTable()方法来执行查询,并获取返回值 

 table = sqlHelper.ExecSelect(Sql, CommandType.Text, sqlParams)

 Return table

 End Function

End Class
Facade层:

Public Class LoginFacade

 Public Function CheckUser(ByVal UserInfo As Model.EntityUserInfo) As Boolean

 用于检查用户是否存在 

 Dim IsUserExists As New BLL.LoginBLL()

 Dim flag As Boolean

 flag = IsUserExists.IsUserExists(UserInfo)

 If flag = True Then

 Return True

 Else

 Return False

 End If

 End Function

 检查密码是否正确 

 Public Function CheckPwd(ByVal UserInfo As Model.EntityUserInfo) As DataTable

 Dim IsPwd As New BLL.LoginBLL()

 Dim table As DataTable

 table = IsPwd.isPWDright(UserInfo)

 Return table

 End Function

End Class
Factory层:

Public Class LoginFactory

 Dim strDB As String = System.Configuration.ConfigurationSettings.AppSettings("DBString")

 Private Shared ReadOnly AssemblyName As String = "DAL" 数据程序集名称

 Public Function CreateUserInfo() As IDAL.IUserinfo

 CType是一个内联函数,将前部分的表达式转换为后面的类型 

 Return CType(Assembly.Load("DAL").CreateInstance("DAL" "." "LoginDAL"), IDAL.IUserinfo) 返回Iuserinfo 

 End Function

End Class

Public Class frmLogin

 Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click

 If txtUserName.Text = "" Then

 MessageBox.Show("请输入用户名!", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

 txtUserName.Text = ""

 txtUserName.Focus()

 Exit Sub

 ElseIf txtPassword.Text = "" Then

 MessageBox.Show("请输入密码!", "", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

 txtPassword.Text = ""

 txtPassword.Focus()

 Exit Sub

 End If

 定义一个外观层的对象 

 Dim FacadeLogin As New Facade.LoginFacade

 Dim UserInfo As New Model.EntityUserInfo

 UserInfo.ID = txtUserName.Text

 UserInfo.Password = txtPassword.Text

 Dim strResult1 As Boolean

 strResult1 = FacadeLogin.CheckUser(UserInfo) 将U层的文本框的内容传入外观层,然后通过外观层传入B层进行判断 

 If strResult1 = False Then

 MsgBox("用户不存在或者密码不正确!")

 txtUserName.Text = ""

 txtPassword.Text = ""

 txtUserName.Select()

 txtUserName.Focus()

 End If

 Dim table As DataTable

 table = FacadeLogin.CheckPwd(UserInfo)

 If Trim(txtPassword.Text) = Trim(table.Rows(0).Item(3)) Then

 MsgBox("登陆成功!")

 Me.Hide()

 MDIfrmMain.Show()

 txtUserName.Text = ""

 txtPassword.Text = ""

 End If

 End Sub

End Class

学习心得:

 其实,在一次次重复之后,对于这个实例也就不再那么迷惑了。但从画图的那一个过程来看,自己还是很吃力的,自己还是没有完全理解,但最起码比刚开始要好很多,通过后面的功能的实现,自己也是会越来越熟悉的,到时候,理解得肯定也就更加深刻了。

 机房重构,没有捷径,只有一步一步自己往上爬,蜗牛精神,继续保持下去~~


非常接地气的架构和分层方法,值得学习! 1、背景 说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。 看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,controller做的逻辑比service还多,service往往当成透传了,这其实是很多人开发代码都没有注意到的地方,反正功能也能用,至于放哪无所谓呗。这样往往造成后面代码无法复用,层级关系混乱,对后续代码的维护非常麻烦。