第一天 -- 《2014-07-15 三层架构》1 -- 三层架构基本概念、一个登陆小例子
一、上午《04、项目功能大致说明》--《09、使用三层实现登录》
1、三层的基本概念,为什么要用三层?三层的职责各是什么?
(定义来自百度百科):
三层架构:通常意义上的三层架构就是将整个业务应用划分为:表示层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的即为了“高内聚低耦合”的思想。在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。
另外:参考《三层架构(我的理解及详细分析)》

三层的职责:
UI(表示层):主要是指与用户交互的界面。用于接收用户输入的数据和显示处理后用户需要的数据。
BLL:(业务逻辑层):UI层和DAL层之间的桥梁。实现业务逻辑,具体包含数据的:验证、计算、业务规则等等。
DAL:(数据访问层):与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库。(当然这些操作都是基于UI层的。用户的需求反映给界面(UI),UI反映给BLL,BLL反映给DAL,DAL进行数据的操作,操作后再一一返回,直到将用户所需数据反馈给用户)SQL语句、ADO.Net的类一般只应该出现在DAL中。

每一层都各负其责,那么该如何将三层联系起来呢?
1>单向引用(见下图)
2>这时候实体层(Entity)来了。(注:当然,实体层的作用不止这些)
Entity(实体层):它不属于三层中的任何一层,但是它是必不可少的一层。
Entity在三层架构中的作用:
1,实现面向对象思想中的"封装";
2,贯穿于三层,在三层之间传递数据;
(注:确切的说实体层贯穿于三层之间,来连接三层)
3,对于初学者来说,可以这样理解:每张数据表对应一个实体,即每个数据表中的字段对应实体中的属性。
(注:当然,事实上不完全是这样。为什么呢?1>,可能我们需要的实体在数据表对应的实体中并不存在;2>,我们完全可以将所有数据表中的所有字段都放在一个实体里)
4,每一层(UI—>BLL—>DAL)之间的数据传递(单向)是靠变量或实体作为参数来传递的,这样就构造了三层之间的联系,完成了功能的实现。
但是对于大量的数据来说,用变量做参数有些复杂,因为参数量太多,容易搞混。比如:我要把员工信息传递到下层,信息包括:员工号、姓名、年龄、性别、工资....用变量做参数的话,那么我们的方法中的参数就会很多,极有可能在使用时,将参数匹配搞混。这时候,如果用实体做参数,就会很方便,不用考虑参数匹配的问题,用到实体中哪个属性拿来直接用就可以,很方便。这样做也提高了效率。
(注:这里为什么说可以暂时理解为每个数据表对应一个实体??答:大家都知道,我们做系统的目的,是为用户提供服务,用户可不关心你的系统后台是怎么工作的,用户只关心软件是不是好用,界面是不是符合自己心意。用户在界面上轻松的增、删、改、查,那么数据库中也要有相应的增、删、改、查,而增删改查具体操作对象就是数据库中的数据,说白了就是表中的字段。所以,将每个数据表作为一个实体类,实体类封装的属性对应到表中的字段,这样的话,实体在贯穿于三层之间时,就可以实现增删改查数据了)
综上所述:三层及实体层之间的依赖关系:

2、三层架构的优缺点
优点
(1)、开发人员可以只关注整个结构中的其中某一层;
(2)、可以很容易的用新的实现来替换原有层次的实现;
(3)、可以降低层与层之间的依赖;
(4)、有利于标准化;
(5)、利于各层逻辑的复用。
(6)、结构更加的明确
(7)、在后期维护的时候,极大地降低了维护成本和维护时间
缺点
(1)、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
(2)、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。
(3)、增加了开发成本。
3、三层架构实现用户登录
<1>UI层采集用户输入,最后反馈是否登录成功
1 //UI层主要代码 2 ... 3 //收集用户数据 4 string name = txtUserName.Text.Trim(); 5 string pass = txtPwd.Text.Trim(); 6 7 BLL.PersonManager pm = new BLL.PersonManager(); 8 if (pm.Login(name, pass))//请求BLL层处理登录验证 9 { 10 MessageBox.Show("ok"); 11 } 12 else 13 { 14 MessageBox.Show("no"); 15 } 16 17 ...
<2>BLL层验证、计算用户数据(业务处理),请求DAL读取数据库(或文件)中数据。
1 //BLL层主要代码 2 public bool Login(string name, string pwd) 3 { 4 DAL.PersonService ps = new DAL.PersonService(); 5 int num = (int)ps.Login(name, pwd);//请求DAL读取数据库数据(存在多少条记录) 6 return num == 1; //因为ploginname列(登录名)在数据库表中是唯一键所以只可能存在一条 7 }
<3>DAL层读取数据库(或文件)中数据。一般DAL层会有SqlHelper这样的帮助类。
1 //DAL层主要代码 2 public object Login(string name, string pwd) 3 { 4 string sql = "select count(*) from person where PLoginName = @name and PPwd = @pwd"; 5 SqlParameter[] ps = { 6 new SqlParameter("name",name), 7 new SqlParameter("pwd",pwd) 8 }; 9 return SqlHelper.ExecuteScalar(sql, ps); 10 } 11 12 13 14 15 //DAL层的SqlHelper类主要代码 16 ... 17 private static readonly string connStr = "Data Source=.;Initial Catalog=ItcastSIM;Integrated Security=True"; 18 19 public static object ExecuteScalar(string sql, params SqlParameter[] ps) 20 {//执行sql语句,返回一个值(首行首列值) 21 using(SqlConnection conn = new SqlConnection(connStr)) 22 { 23 conn.Open(); 24 SqlCommand comm = new SqlCommand(sql, conn); 25 comm.Parameters.AddRange(ps); 26 return comm.ExecuteScalar(); 27 } 28 } 29 ...
二、下午《01、引入实体层》--《03、在三层中配置文件在UI层创建》-- 重构代码,引入实体层,一个登陆小例子
1、引入实体类
重构代码使三层架构中处理的数据以实体对象和实体对象集合的形式出现,因为在架构中使用DataSet和DataTable有很多不方便的地方(比如需要频繁的数据转换,列中存储的数据以object形式存在)。
多数情况下实体类直接映射数据库表/视图,表名作C#类名,列名做C#属性名(C#字段加前缀)。有时根据UI需要,也会写一些View-Model类,要么是实体类的一部分字段,要么是多个实体类的部分字段。
1 //登陆小例子,实体层主要代码 2 public class Person 3 { 4 public Person() 5 { } 6 7 private int _pid; 8 private int _pcid; 9 private int _ptype = 1; 10 private string _ploginname; 11 private string _pcname; 12 private string _ppyname; 13 private string _ppwd; 14 private bool _pgender = false; 15 private string _pemail; 16 private string _pareas; 17 private bool _pisdel = false; 18 private DateTime _paddtime = DateTime.Now; 19 /// <summary> 20 /// 学生表ID 21 /// </summary> 22 public int PID 23 { 24 set { _pid = value; } 25 get { return _pid; } 26 } 27 /// <summary> 28 /// 所属班级ID(外键) 29 /// </summary> 30 public int PCID 31 { 32 set { _pcid = value; } 33 get { return _pcid; } 34 } 35 36 //其他属性略,都可以通过《动软代码生成器》生成。 37 ... 38 }
2、重构UI层
采集用户输入信息,请求BLL层获取实体对象,最后反馈登录信息。
1 //登录窗体FrmLogin.cs主要代码。该窗体在Main()入口方法中以ShowDialog()方式显示。 2 ... 3 private void btnLogin_Click(object sender, EventArgs e) 4 {//登录按钮 5 string name = this.txtUserName.Text.Trim(); 6 string pass = this.txtPwd.Text.Trim(); 7 //创建业务层对象 8 BLL.PersonManager pm = new BLL.PersonManager(); 9 //返回一个实体对象 10 MODEL.Person per = pm.Login(name); 11 if (per == null) 12 { 13 MessageBox.Show("登录名不存在"); 14 } 15 else 16 { 17 if (per.PPwd == pass) 18 { 19 MessageBox.Show("登录成功,欢迎你:" + per.PCName); 20 this.DialogResult = DialogResult.OK; 21 } 22 else 23 { 24 MessageBox.Show("密码错误"); 25 } 26 } 27 } 28 29 private void btnCancel_Click(object sender, EventArgs e) 30 {//取消登录 31 this.DialogResult = DialogResult.Cancel; 32 } 33 ...
3、重构BLL层
BLL层请求DAL获取所需实体对象。
1 //BLL层PersonManager类主要代码如下: 2 ... 3 //创建一个DAL对象做字段。因为该类以后很多方法都需要调用DAL中的方法 4 private DAL.PersonSerivce ps = new DAL.PersonSerivce(); 5 6 public MODEL.Person Login(string name) 7 { 8 return ps.Login(name); 9 } 10 11 ...
4、重构DAL层
DAL层根据BLL层的请求,组织sql语句和sql参数,调用SqlHelper相关方法从数据库读取所需数据。最后根据这些数据创建实体对象(或实体对象集合)返回给BLL层。
1 //DAL层PersonSerivce类的主要代码: 2 ... 3 public MODEL.Person Login(string name) 4 { 5 string sql = @"select pid, pcid, ptype, ploginname, pcname, ppyname, ppwd, pgender, pemail, pareas, pisdel, paddtime 6 from person where ploginname = @name and pisdel = 0";//检索指定名字,未标记删除的记录。 7 SqlParameter p = new SqlParameter("name", name); 8 //声明实体对象 9 MODEL.Person per = null; 10 using (SqlDataReader reader = SqlHelper.ExecuteReader(sql, p)) 11 {//让SqlHelper类的方法返回一个读取器。因为sql语句的组织和实体对象的创建工作都应该放到DAL中,要放在一起。而SqlHelper类的职责应该是运用ADO.NET读取数据库数据给DAL层。 12 if (reader.Read()) //如果有数据,也就一行。所以没有必要循环 13 {//如果存在记录,读取一条 14 per = new MODEL.Person(); //下面为实体的各个属性(字段)赋值 15 16 per.PID = (int)reader["pid"]; 17 per.PCID = (int)reader["PCID"]; 18 per.PType = (int)reader["PType"]; 19 per.PLoginName = reader["ploginname"].ToString(); 20 per.PCName = reader["PCName"].ToString(); 21 per.PPYName = reader["PPYName"].ToString(); 22 per.PPwd = reader["PPwd"].ToString(); 23 per.PGender = (bool)reader["PGender"]; 24 per.PEmail = reader["PEmail"].ToString(); 25 per.PAreas = reader["PAreas"].ToString(); 26 per.PIsDel = (bool)reader["PIsDel"]; 27 per.PAddTime = Convert.ToDateTime(reader["PAddTime"]); 28 } 29 } 30 return per; 31 } 32 33 ...
5、SqlHelper类
1 //SqlHelper类主要代码: 2 3 class SqlHelper 4 { 5 //从应用程序配置文件中读取连接字符串 6 private static readonly string connStr = 7 System.Configuration.ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; 8 9 //返回数据读取器对象 10 public static SqlDataReader ExecuteReader(string sql, params SqlParameter[] ps) 11 { 12 //这里不能Dispose清理连接通道,因为清理了连接通道(也就Close了通道),读取器对象也将被清理(和关闭)。 13 SqlConnection conn = new SqlConnection(connStr); 14 conn.Open(); 15 SqlCommand comm = new SqlCommand(sql, conn); 16 comm.Parameters.AddRange(ps); 17 //参数说明:以后清理(关闭)读取器的时候,使用的连接通道也会被自动清理(关闭) 18 return comm.ExecuteReader(CommandBehavior.CloseConnection); 19 } 20 21 }
注意:
<1>实际上尽量不要让SqlHelper类方法返回SqlDataReader对象给DAL,而应该返回DataTable。然后在DAL层将DataTable转为实体对象集合
<2>在上例中如果要返回SqlDataReader对象,一定记得Connection不能在SqlHelper中关闭,而且调用Command.ExecuteReader()方法时,一定要指定CommandBehavior.CloseConnection参数。这样当读取器被(DAL层)清理时,连接也会自动被清理掉。(当然清理Connection时,连接会被Close掉)
<3>关于Connection和DataReader的清理和关闭。可参考《第三天 -- 《2014-07-08 ADO》2 --ADO.NET的基本操作》的第三、第四节
<4>在UI层(可执行程序)的app.config配置文件中做的配置,可以在其他类库程序集中通过ConfigurationManager访问到。
浙公网安备 33010602011771号