不知道大家还记不记得之前学习的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 Try 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 FunctionBLL层:
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 ClassDAL层:
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 style="font-family: Arial, Helvetica, sans-serif;">'中间变量,用于储存从数据库中查找到的信息</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 ClassFacade层:
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 ClassFactory层:
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 ClassUI层:
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
学习心得:
其实,在一次次重复之后,对于这个实例也就不再那么迷惑了。但从画图的那一个过程来看,自己还是很吃力的,自己还是没有完全理解,但最起码比刚开始要好很多,通过后面的功能的实现,自己也是会越来越熟悉的,到时候,理解得肯定也就更加深刻了。 机房重构,没有捷径,只有一步一步自己往上爬,蜗牛精神,继续保持下去~~