在上篇文章写到我们为什么要分层.有很多读者提出来很多宝贵的意见.让我受益匪浅,深深的感觉到自己的水平"还有很大的提升空间".首先感谢这些朋友们,我会进一步总结完善自己的想法.
截取了部分朋友的留言,感谢他们:
这次我用对比的方式描述一下,分层到底分出了什么.俗话说:有分必有合,那么它是把什么合到了一起.
首先写出两个没有分层的demo:
<1>查询信息demo
1: Public Class Form2<!--CRLF-->
2: Private sqlCon As String = "Data Source=LSH;Initial Catalog=ComputerLab;User ID=sa;Password=123456"<!--CRLF-->
3: '查询数据库信息信息<!--CRLF-->
4: Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click<!--CRLF-->
5:
<!--CRLF-->
6: Dim conStr As New SqlConnection '数据库连接对象<!--CRLF-->
7: Dim sqlCom As SqlCommand '数据库执行对象<!--CRLF-->
8: Dim Res As DialogResult '消息提示框返回类型<!--CRLF-->
9: Dim dr As SqlDataReader 'dataReader对象<!--CRLF-->
10: Dim dt As New DataTable 'Datatable对象<!--CRLF-->
11:
<!--CRLF-->
12: Dim sql As String = "select * from TableName where Name=@name" 'sql插入语句<!--CRLF-->
13: conStr.ConnectionString = sqlCon '给数据库连接对象赋值<!--CRLF-->
14: sqlCom = New SqlCommand(sql, conStr) '给数据库执行对象赋值<!--CRLF-->
15: sqlCom.Parameters.Add("@name", SqlDbType.VarChar, TextBox1.Text) '给sql语句参数赋值<!--CRLF-->
16:
<!--CRLF-->
17: Res = MessageBox.Show("是否添加", "提示", MessageBoxButtons.OKCancel)<!--CRLF-->
18: '判断是否查询<!--CRLF-->
19: If Res = DialogResult.Yes Then<!--CRLF-->
20: Try<!--CRLF-->
21: conStr.Open()
<!--CRLF-->
22: dr = sqlCom.ExecuteReader '执行查询语句<!--CRLF-->
23: dt.Load(dr)
<!--CRLF-->
24: Catch ex As Exception<!--CRLF-->
25: Throw ex<!--CRLF-->
26: Finally<!--CRLF-->
27: If Not IsNothing(conStr) Then '如果数据库打开,则关闭数据库<!--CRLF-->
28: conStr.Close()
<!--CRLF-->
29: End If<!--CRLF-->
30: End Try<!--CRLF-->
31: End If<!--CRLF-->
32:
<!--CRLF-->
33: MsgBox(dt.Rows.Count) '显示查询到的行数<!--CRLF-->
34: End Sub<!--CRLF-->
<2>添加信息demo
1: Public Class Form1<!--CRLF-->
2: Private sqlCon As String = "Data Source=LSH;Initial Catalog=ComputerLab;User ID=sa;Password=123456"<!--CRLF-->
3: '向数据库添加信息<!--CRLF-->
4: Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click<!--CRLF-->
5: Dim bln As Boolean = False '存储返回值<!--CRLF-->
6: Dim conStr As New SqlConnection '数据库连接对象<!--CRLF-->
7: Dim sqlCom As SqlCommand '数据库执行对象<!--CRLF-->
8: Dim Res As DialogResult '消息提示框返回类型<!--CRLF-->
9:
<!--CRLF-->
10: Dim sql As String = "insert into TableName(Name) value(@Name)" 'sql插入语句<!--CRLF-->
11: conStr.ConnectionString = sqlCon '给数据库连接对象赋值<!--CRLF-->
12: sqlCom = New SqlCommand(sql, conStr) '给数据库执行对象赋值<!--CRLF-->
13: sqlCom.Parameters.Add("@Name", SqlDbType.VarChar, TextBox1.Text) '给sql语句参数赋值<!--CRLF-->
14:
<!--CRLF-->
15: Res = MessageBox.Show("是否添加", "提示", MessageBoxButtons.OKCancel)<!--CRLF-->
16: '判断是否同意插入<!--CRLF-->
17: If Res = DialogResult.Yes Then<!--CRLF-->
18: Try<!--CRLF-->
19: conStr.Open()
<!--CRLF-->
20: bln = sqlCom.ExecuteNonQuery '执行插入语句<!--CRLF-->
21: Catch ex As Exception<!--CRLF-->
22: bln = False<!--CRLF-->
23: Finally<!--CRLF-->
24: If Not IsNothing(conStr) Then '如果数据库打开,则关闭数据库<!--CRLF-->
25: conStr.Close()
<!--CRLF-->
26: End If<!--CRLF-->
27: End Try<!--CRLF-->
28: End If<!--CRLF-->
29:
<!--CRLF-->
30: MsgBox(bln) '提示返回信息<!--CRLF-->
31: End Sub<!--CRLF-->
32: End Class<!--CRLF-->
上面的两个没有分层的代码,当然在功能的实现上是完全没有问题.但是这样设计带来了很多的危机:
(1):写这个模块的人必须是一个能力非常强的人,因为他需要操作数据库,理解业务逻辑
(2):如果有另为一个模块需要这两个操作数据库的方法,只能重写或者调用该窗体下的方法.
(3):如果业务逻辑稍有变动,就需要拆开这个模块,重新编写,对于重新设计的人来说这个模块的所有设计都是可见的,很是不安全.
(4):如果要更换或者改动数据库,结果就是每一个模块都要重新编写
(5):如果有另为一个和这个相似的工程,想要复用以前的东西,难度很大
对于一个公司来说,当然不希望发生这样的事情.所以,合理的分层,降低系统的耦合性是必要的.
第一步:把数据库操作提取出来.
对每一个表,把完全对他的操作单独写成类也就是我们的DAO,数据访问类
每一个表对应一个DAO,每个DAO里面包含该数据库的所有增,删,查,改操作.这样做的好处就是对数据库的操作完全独立,达到功能单一.降低了和其他模块的耦合,写代码的时候,程序员不需要了解业务逻辑.
而且,在维护上和扩展上,如果对数据库改动,就会有针对性的去修改DAO.
当然,在设计的时候,我们尽量去封装不变的操作,这样不但能减少代码量,而且能达到很好的复用效果.在数据库操作中,对于数据库的打开,执行,关闭基本上都是一样的.所以我们单独提取出来sqlHelper(数据库助手类)
1: Imports System.Data.SqlClient<!--CRLF-->2: Imports System.Configuration<!--CRLF-->3:
<!--CRLF-->4: ''' <summary><!--CRLF-->5: ''' 运行数据库<!--CRLF-->6: ''' </summary><!--CRLF-->7: Public Class SqlHelp<!--CRLF-->8: Private cmd As SqlCommand 'sqlcommand对象<!--CRLF-->9: Private con As SqlConnection 'sqlconnection对象<!--CRLF-->10: Private dr As SqlDataReader 'sqlDataReader对象<!--CRLF-->11: ''' <summary><!--CRLF-->12: ''' 获取连接字符串<!--CRLF-->13: ''' </summary><!--CRLF-->14: ''' <returns>连接字符串</returns><!--CRLF-->15: ''' <remarks></remarks><!--CRLF-->16: Public Function GetCon() As SqlConnection<!--CRLF-->17: Dim conStr As String<!--CRLF-->18: conStr = System.Configuration.ConfigurationManager.AppSettings("ConnStr")<!--CRLF-->19:
<!--CRLF-->20: con = New SqlConnection<!--CRLF-->21: con.ConnectionString = conStr
<!--CRLF-->22:
<!--CRLF-->23: '打开数据库<!--CRLF-->24: If (con.State = ConnectionState.Closed) Then<!--CRLF-->25: con.Open()
<!--CRLF-->26: End If<!--CRLF-->27:
<!--CRLF-->28: Return con<!--CRLF-->29:
<!--CRLF-->30: End Function<!--CRLF-->31:
<!--CRLF-->32: ''' <summary><!--CRLF-->33: ''' 增 删 改方法<!--CRLF-->34: ''' </summary><!--CRLF-->35: ''' <param name="sqlStr">数据库语句 活存储过程名</param><!--CRLF-->36: ''' <param name="Param">参数数组</param><!--CRLF-->37: ''' <param name="commandType">数据库字符串 或者存储过程名 类型</param><!--CRLF-->38: Public Function ExecuteNonQuery(ByVal sqlStr As String, ByVal Param() As SqlParameter, ByVal commandType As CommandType) As Boolean<!--CRLF-->39:
<!--CRLF-->40: cmd = New SqlCommand(sqlStr, Me.GetCon)<!--CRLF-->41: cmd.CommandType = commandType
<!--CRLF-->42:
<!--CRLF-->43: '添加参数<!--CRLF-->44: If Param IsNot Nothing Then<!--CRLF-->45: cmd.Parameters.AddRange(Param)
<!--CRLF-->46: End If<!--CRLF-->47:
<!--CRLF-->48: '执行语句<!--CRLF-->49: Try<!--CRLF-->50: Return CBool(cmd.ExecuteNonQuery)<!--CRLF-->51: Catch ex As Exception<!--CRLF-->52: Return False<!--CRLF-->53: Finally<!--CRLF-->54: If Not IsNothing(con) Then<!--CRLF-->55: con.Close()
<!--CRLF-->56: End If<!--CRLF-->57: End Try<!--CRLF-->58:
<!--CRLF-->59: End Function<!--CRLF-->60:
<!--CRLF-->61: ''' <summary><!--CRLF-->62: ''' 查询<!--CRLF-->63: ''' </summary><!--CRLF-->64: ''' <param name="sqlStr">sql语句 或者存储过程名</param><!--CRLF-->65: ''' <param name="Param">参数数组</param><!--CRLF-->66: ''' <param name="commandType">执行类型</param><!--CRLF-->67: Public Function ExecuteQuery(ByVal sqlStr As String, ByVal Param() As SqlParameter, ByVal commandType As CommandType) As DataTable<!--CRLF-->68: Dim dt As New DataTable<!--CRLF-->69: cmd = New SqlCommand(sqlStr, Me.GetCon)<!--CRLF-->70: cmd.CommandType = commandType
<!--CRLF-->71:
<!--CRLF-->72: '添加参数<!--CRLF-->73: If Param IsNot Nothing Then<!--CRLF-->74: cmd.Parameters.AddRange(Param)
<!--CRLF-->75: End If<!--CRLF-->76:
<!--CRLF-->77: '执行语句<!--CRLF-->78: Try<!--CRLF-->79: dr = cmd.ExecuteReader
<!--CRLF-->80: dt.Load(dr)
<!--CRLF-->81: Catch ex As Exception<!--CRLF-->82: Return Nothing<!--CRLF-->83: Finally<!--CRLF-->84: If Not IsNothing(con) Then<!--CRLF-->85: con.Close()
<!--CRLF-->86: End If<!--CRLF-->87: End Try<!--CRLF-->88: Return dt<!--CRLF-->89: End Function<!--CRLF-->提取后数据库访问层代码如下:
<!--CRLF-->1: Imports SqlHelper<!--CRLF-->2: Imports System.Data.SqlClient<!--CRLF-->3: Public Class DAL<!--CRLF-->4: Private sqlHelper As New SqlHelp<!--CRLF-->5: ''' <summary><!--CRLF-->6: ''' 查询记录<!--CRLF-->7: ''' </summary><!--CRLF-->8: ''' <param name="strSelect">关键字</param><!--CRLF-->9: ''' <returns>记录集</returns><!--CRLF-->10: ''' <remarks></remarks><!--CRLF-->11: Public Function SelectInfo(ByVal strSelect As String) As DataTable<!--CRLF-->12: 'sql查询语句<!--CRLF-->13: Dim sql As String = "select * from TableName where Name=@name"<!--CRLF-->14: '向sql参数中添加实参<!--CRLF-->15: Dim Para() As SqlParameter = {New SqlParameter("@name", strSelect)}<!--CRLF-->16: '调用sqlhelper执行数据库操作<!--CRLF-->17: Return sqlHelper.ExecuteQuery(sql, Para, Data.CommandType.Text)<!--CRLF-->18: End Function<!--CRLF-->19: ''' <summary><!--CRLF-->20: ''' 添加记录<!--CRLF-->21: ''' </summary><!--CRLF-->22: ''' <param name="strAdd">记录信息</param><!--CRLF-->23: ''' <returns>是否成功</returns><!--CRLF-->24: ''' <remarks></remarks><!--CRLF-->25: Public Function AddInfo(ByVal strAdd As String) As Boolean<!--CRLF-->26: 'sql插入语句<!--CRLF-->27: Dim sql As String = "insert into TableName(Name) value(@Name)"<!--CRLF-->28: '向sql参数中添加实参<!--CRLF-->29: Dim Para() As SqlParameter = {New SqlParameter("@name", strAdd)}<!--CRLF-->30: '调用sqlhelper执行数据库操作<!--CRLF-->31: Return sqlHelper.ExecuteNonQuery(sql, Para, Data.CommandType.Text)<!--CRLF-->32: End Function<!--CRLF-->33: End Class<!--CRLF-->
第二:根据业务逻辑,个人习惯和用例相对应,把相关的逻辑判断,比如添加记录在什么情况下判断,添加前要判断记录是否存在等放到BLL层.
但是对于数据的有效性校验一般还是放在UI层,也可以有其他更好的方法,这些我在以后会总结出来.
代码如下:
1: Public Class BLL<!--CRLF-->2: Private dal As New DAL<!--CRLF-->3: ''' <summary><!--CRLF-->4: ''' 业务逻辑层,添加方法<!--CRLF-->5: ''' </summary><!--CRLF-->6: ''' <param name="strAdd">添加信息</param><!--CRLF-->7: ''' <remarks></remarks><!--CRLF-->8: Public Sub Add(ByVal strAdd As String)<!--CRLF-->9: '定义用于存储查询结果的记录集<!--CRLF-->10: Dim dt As New DataTable<!--CRLF-->11: '执行查询,判断添加的信息是否已经存在<!--CRLF-->12: dt = dal.SelectInfo(strAdd)
<!--CRLF-->13: If dt.Rows.Count > 0 Then<!--CRLF-->14: Throw New Exception("记录已存在")<!--CRLF-->15: Else<!--CRLF-->16: '添加记录,返回是否成功<!--CRLF-->17: If dal.AddInfo(strAdd) = True Then<!--CRLF-->18: Throw New Exception("添加成功")<!--CRLF-->19: Else<!--CRLF-->20: Throw New Exception("添加失败")<!--CRLF-->21: End If<!--CRLF-->22: End If<!--CRLF-->23:
<!--CRLF-->24: End Sub<!--CRLF-->25:
<!--CRLF-->26: End Class<!--CRLF-->第三:剩下的UI层里,除了判断数据有效性以为,只要调用bll的添加方法就可以了.这样制作UI的人就会减轻了很大的压力,让他们专心做美工或者其他.
代码如下:
<!--CRLF-->1: Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click<!--CRLF-->2: Dim bll As New BLL<!--CRLF-->3: '调用bll层添加方法<!--CRLF-->4: Try<!--CRLF-->5: bll.Add(TextBox1.Text)
<!--CRLF-->6: Catch ex As Exception<!--CRLF-->7: '显示添加结果给用户<!--CRLF-->8: MessageBox.Show(ex.Message)
<!--CRLF-->9: End Try<!--CRLF-->10: End Sub<!--CRLF-->
当然三层方式还有其他一些技巧,比如为DAL加一层接口,用抽象工厂加反射的方法实现数据库的灵活操作,替换.这里先不叙述.
上述都是个人的一点总结,欢迎大家指导
接下文:解析三层架构(3)--实体类与面向对象