在软件体系架构中分层结构是最常见的,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,自上至下分别是:表示层、业务逻辑层、数据持久层,这里所说的三层是按照功能将系统在开发过程中进行划分,但是这里少了很重要的一层就是实体层,在三层架构中实体层没有显著的功能,它主要的功能是负责各层间参数的传递。在业务层和数据访问层中定义的每个函数所用的参数来自实体层,函数间参数的传递也是使用的实体层。
三层结构+抽象工厂+反射的包图:
上面的包图中IDAL接口封装了DAL中对数据库操作的方法,DAL继承了IDAL中的接口类实现了具体的操作,Factory工厂包使用反射获得了DAL中具体数据库表的实例。当BLL层需要使用数据库中数据时,只需调用Factory包中的方法返回所需要的数据库表。这样的实现了面向接口编程,降低了程序和数据库的耦合程度。在更换数据库时只需扩展DAL包中的类图,继承IDAL保重的类,遵守了扩展开放修改封闭的原则。
一 .NET程序编译机制
解决方案和程序集是.NET程序的两个重要结构,当创建一个新项目时.NET会让我们同时创建解决方案和程序集,一个解决方案内能够同时包含多个程序集。
1、解决方案
解决方案封存了程序集和程序集运行需要的环境,在创建解决方案时.NET会在指定目录下创建一个以解决方案命名的项目文件,项目文件内封存了项目所需的程序集。
在生成解决方案时,每个程序集会在自己的运行环境下生成所需的程序组件,如果是类或接口之类的文件,程序集编译后会生成DLL文件,生成的DLL文件可以被其他程序调用。
2、程序集
程序集封装了程序运行所需的组件,一个程序集中包括程序运行的组成文件,如:接口、类、Html文件等。在生成解决方案(编译生成项目)后程序集一般会生成DLL文件、exe文件或COM组件等程序文件,而解决方案封存了这些程序文件,一般情况下启动项目会生成EXE文件,其它引用项目会生成DLL文件。
需要重点强调的是程序集的引用关系,.NET项目的引用关系调用的并不是地址而是把被引用的文件封存在程序集Bin文件中,也就是说在程序集Bin文件中会生成被引用文件的源文件,这样程序集在运行时会在自己的bin文件中查找被引用的文件,而不用去其它地方查找,使得.NET能够更高效的运行。
了解了.NET程序编译机制我们就可以很简单的使用反射功能来实现程序的解耦,在上面的包图中Factory包没有引用DAL包但使用反射可以实例化DAL包,在生成解决方案后要把生成的DAL的DLL文件手动放到启动项目(UI包)或Factory包的Bin文件中,否则会在使用反射时弹出错误,提示“未能加载文件或程序集“DAL”或它的某一个依赖项。系统找不到指定的文件。”
二、登陆具体实现
UI层
负责启动系统,显示数据,实现UI逻辑。系统的配置文件写入该层,例如:为反射提供字符串或者为连接数据库提供字符串的App.config必须放在该层,因为系统在调用时会在启动程序集下面查找这些文件,这牵扯到了.NET程序的运行机制问题具体以后再总结,如果没有找到将会导致程序出错。
'====================================================================================== '作 者: Zhang '编写时间:2013-05-25 '功 能:UI层,实现系统登陆 '====================================================================================== Private Sub btnLoginSystem_Click(sender As Object, e As EventArgs) Handles btnLoginSystem.Click '判断是否已经输入用户名和密码 If txtUsername.Text.Trim = "" Then '提示输入用户名 MsgBox("请输入用户名!", vbOKOnly, "提示") Exit Sub Else If txtPassword.Text = "" Then '提示输入密码 MsgBox("请输入密码!") Exit Sub End If End If Dim strUsername As String = txtUsername.Text.Trim '定义保存用户名的字符串 Dim strPassword As String = txtPassword.Text '定义保存密码的字符串 Dim EnAdmin As New Entity.AdminInfoEntity '定义实体传参的对象 Dim LoginMan As New BLL.AdminBLL '定义业务逻辑层 '向实体层传递使用的参数 EnAdmin.ad_Username = strUsername EnAdmin.ad_Password = strPassword Try '实现登陆过程 Dim Admin1 As Entity.AdminInfoEntity = LoginMan.Login(EnAdmin) '登陆成功后显示主窗体,按照用户级别分别显示不同的功能菜单 If Admin1.ad_Level = "Operator" Then '如果是普通的操作员,高级功能将不能使用 frmMain1.tspMenu2.Visible = False frmMain1.tspMenu3.Visible = False Else If Admin1.ad_Level = "Admin" Then frmMain1.tspMenu3.Visible = False End If End If frmMain1.Show() '显示主窗体 mo_Username = strUsername '保存登陆的用户名 Me.Close() '关闭登陆窗体 Catch ex As Exception MsgBox(ex.Message, vbOKOnly, "提示信息") '错误提示框 End Try End Sub
BLL层
被UI层调用,不知道UI层的存在。负责业务逻辑,在调用具体的数据库表中的数据时,应定义接口类,并使用工厂类中的DataAccess类方法反射实例化相应的DAL类。在使用时必须引用Entity实体程序集、Factory工厂程序集、IDAL数据库接口程序集。
Public Class AdminBLL
Dim Mflag As Boolean = False
Dim DataAccess As New Factory.DataAccess '定义数据库访问工厂类
'======================================================================================
'编写时间:2013-05-25
'函数类别:BLL层,登陆判断函数
'函数功能:主要对传递来的Admin信息进行判断,并抛出相应的错误异常
'======================================================================================
Public Function Login(ByRef Admin As Entity.AdminInfoEntity) As Entity.AdminInfoEntity
Dim SelectAdmin As IDAL.IAdminIDAL = DataAccess.CreateAdmin '定义管理员表类,用于检索管理员
Dim DALOnwork As IDAL.IOnWorkDAL = DataAccess.CreateOnwork '定义正在工作管理员表类
Dim Admin1 As Entity.AdminInfoEntity = Nothing '定义传参实体
Dim Onwork As New Entity.OnWorkEntity '定义正在工作管理员表实体
Onwork.on_Username = Admin.ad_Username '为Onwork的用户名传参设置值
Admin1 = SelectAdmin.SelectAdminUser(Admin) '按照用户名在数据库中查找相应记录
'判断用户信息是否存在
If Not Admin1 Is Nothing Then
'判断密码是否输入正确
Admin1 = Nothing
Admin1 = SelectAdmin.SelectAdmin(Admin)
If Not Admin1 Is Nothing Then
Mflag = True
Else
Throw New Exception("密码错误,请重新输入密码!") '抛出异常
End If
Else
Throw New Exception("用户不存在,请重新输入!") '抛出异常
End If
'实现登录管理
LoginManager(Onwork)
Return Admin1
End Function
'======================================================================================
'编写时间:2013-05-25
'函数类别:BLL层,登陆管理函数,
'函数功能:主要实现登录时对数据库表的操作
'======================================================================================
Public Function LoginManager(Onwork As Entity.OnWorkEntity) As Entity.OnWorkEntity
Dim DALOnwork As IDAL.IOnWorkDAL = DataAccess.CreateOnwork '创建并实例化DALOnwork表
Dim DALWorklog As IDAL.IWorkLogIDAL = DataAccess.CreateWorkLog '创建并实例化DALWorklog表
Dim Mflag As Boolean = False '定义中间参数
Dim Onwork1 As Entity.OnWorkEntity = DALOnwork.SelectLog(Onwork) '定义正在上机的实体类,并检索该用户是否正在操作系统
'在登陆前查询该用户上次是否正常上机
If Onwork1 Is Nothing Then
'没有在上机的话,在Onwork表中插入该管理员上机记录
DALOnwork.InsertLog(Onwork)
Else
'插入教师值班记录
Mflag = DALWorklog.InsertLog(Onwork)
If Mflag = True Then
DALOnwork.DeleteLog(Onwork) '删除正在上机的教师记录
DALOnwork.InsertLog(Onwork) '增加教师新的上机记录
End If
End If
Return Onwork1
End Function
End Class
IDAL层
被BLL层和DAL层调用,提供了DAL层的所需要的接口。该接口降低了项目中各层之间的耦合程度,能够简单的实现更换数据库。
'======================================================================================
'作 者: Zhang
'编写时间:2013-05-25
'类 名 称:类名为IAdminIDAL
'功 能: 管理员类的接口,主要实现对系统的操作
'======================================================================================
Public Interface IAdminIDAL
'查询系统用户名
Function SelectAdminUser(ByRef Admin As Entity.AdminInfoEntity) As Entity.AdminInfoEntity
'查询系统用户名和密码
Function SelectAdmin(ByRef Admin As Entity.AdminInfoEntity) As Entity.AdminInfoEntity
'修改用户密码信息
Function UpdateAdmin(ByRef Admin As Entity.AdminInfoEntity) As Boolean
'按照用户级别查询系统用户
Function SelectUserByLevel(ByRef Admin As Entity.AdminInfoEntity) As DataSet
'删除用户
Function DelectUser(ByRef Admin As Entity.AdminInfoEntity) As Boolean
'新增用户
Function InsertUser(ByRef Admin As Entity.AdminInfoEntity) As Boolean
'获取系统用户
Function SelectAdminName() As String()
'按用户名查询到用户真实姓名
Function SelectTrueName(ByRef Admin As Entity.AdminInfoEntity) As String
End Interface
Factory层
工厂程序集,被BLL层调用,并调用IDAL层,引用反射,使用反射得到具体DAL层类的实例。使用配置文件获取具体的数据库字符串,决定是实例化哪种类型的数据库。
Imports System.Reflection '引用反射
Imports System.Configuration '引用配置文件
Imports IDAL '引用接口程序集
'======================================================================================
'作 者: 张信秀
'编写时间:2013-06-06
'类名称、功能:类名为DataAccess,是封装数据访问的工厂类
'======================================================================================
Public Class DataAccess
Private ReadOnly AssemblyName As String = "DAL" '定义程序集名称
Dim db As String = ConfigurationSettings.AppSettings("DB") '获取配置文件中的字符串
'创建Admin表
Public Function CreateAdmin() As IAdminIDAL
Dim className As String '定义实例化类的名称
Dim Admin As IAdminIDAL '定义类的返回类型
className = AssemblyName + "." + db + "AdminDAL" '为实例化类名称赋值,程序集名称+类名
Admin = CType(Assembly.Load(AssemblyName).CreateInstance(className), IDAL.IAdminIDAL) '反射实例化
Return Admin '返回值
End Function
'创建Onwork表
Public Function CreateOnwork() As IOnWorkDAL
Dim className As String '定义实例化类的名称
Dim OnworkData As IOnWorkDAL '定义返回值
className = AssemblyName + "." + db + "OnWorkDAL" '为实例化类名称赋值,程序集名称+类名
OnworkData = CType(Assembly.Load(AssemblyName).CreateInstance(className), IOnWorkDAL) '反射,实例化具体需要的类
'创建WorkLog表
Public Function CreateWorkLog() As IWorkLogIDAL
Dim className As String '定义实例化类的名称
Dim WorkLogData As IDAL.IWorkLogIDAL '定义返回值
className = AssemblyName + "." + db + "WorkLogDAL" '为实例化类名称赋值,程序集名称+类名
WorkLogData = CType(Assembly.Load(AssemblyName).CreateInstance(className), IWorkLogIDAL) '反射,实例化具体需要的类
Return WorkLogData '返回值
End Function
End Class
DAL层
继承了IDAL层的类,实现了IDAL接口类中的功能,对数据库表进行操作。可以进行扩展,但不支持更改。在编写时可以使用SqlHelper来实现具体的数据库访问,减轻复杂的对象使用。
Imports Microsoft.ApplicationBlocks.Data '引用SqlHelper块
Imports System.Data.SqlClient '引用Sql对象块
Imports IDAL '引用接口程序集
'-------------------------------------------------------------------------
'类属性:管理员信息类,主要保存管理员信息
'类操作:主要实现管理注册信息的操作,对管理员信息进行增加、删除、修改等
'-------------------------------------------------------------------------
Public Class SqlAdminDAL
Implements IAdminIDAL
Dim strCon As New ConnStringDAL '定义数据库连接字符串类,并实例化
'定义查询用户名函数,根据用户名查询用户
Public Function SelectAdminUser(ByRef Admin As Entity.AdminInfoEntity) As Entity.AdminInfoEntity Implements IAdminIDAL.SelectAdminUser
Dim strQry As String = "select * from tblAdmin where ad_Username=@username" '定义查询字符串
'使用Using语句,便于销毁对象
Using sqlCon As New SqlConnection(strCon.ConnectString)
Dim sqlCom As New SqlCommand(strQry, sqlCon) '定义Command对象
Dim Admin1 As Entity.AdminInfoEntity = Nothing '定义返回实体类
sqlCom.Parameters.Add(New SqlParameter("@username", Admin.ad_Username)) '为Command对象增加参数值
sqlCon.Open()
Dim sqlReader As SqlDataReader = sqlCom.ExecuteReader() '定义DataReader对象,读取查询结果集
'按顺序读取数据
While (sqlReader.Read())
'实例化具体的实体类
If Admin1 Is Nothing Then
Admin1 = New Entity.AdminInfoEntity
End If
Admin1.ad_Username = sqlReader.GetString(1) '为实体类用户名赋值
Admin1.ad_Password = sqlReader.GetString(2) '为实体类密码赋值
End While
Return Admin1 '返回实体类
End Using
End Function
End Class
总结: 抽象工厂让具体的创建实例过程与 BLL 层分离, BLL 层是通过 IDAL 接口层操纵实例,通过反射防止了 BLL 层对数据库的依赖。