天天看点

传统的三层架构

三层架构

    三层架构可以说已经是一个比较成熟的分层架构模型,但是还是有部分人弄不清三层架构是怎么回事。就我认识的人之中,有一个被大家认为学的不错的一个同学竟然有一天问老师什么是三层架构。不是说这个学学的怎么样,但至少他自己有时间也可以自己稍微学习一下架构方面的东西。当然我估计不懂的人还不止是他。先来看看我们再熟悉不过的一张图:

传统的三层架构

类似的图估计大家见过很多,但是我们还是一起来看看吧。从底层开始,数据库应该是包括很多种的小到Access大到Oracle都是无关紧要的,大不了我们的DAL内部操作变化了而已,接口是不会发生改变的。而且注意到三个箭头都是指向下层的,说明依赖关系是以上层依赖于紧挨着的下层的。

数据访问层里面放的应该是对元数据 基本操作 ,而不应该包含业务逻辑或者说不能够对数据进行加工。

而业务逻辑层是对让初学者搞不清的,也可以说这个是很需要经验的,那么究竟什么是业务逻辑呢?我们先来说说这个问题,再谈别的。首先看看业务逻辑的组成结构:

传统的三层架构

    读过领域驱动开发的朋友大概不会疑惑领域的作用,我们的业务不是无缘无故产生的,例如我们要给沃尔玛开发收银系统那么我们就要先了解整个沃尔玛销售中所涉及的对象,比如商品、小票、会员、销售等,这些都是领域知识,和业务息息相关。

    其次就是业务规则,比如说我们在销售的时候会出现退货的情况,但是不是什么时候都可以退货,假设沃尔玛规定7天之内可以退货那么7天的限制 就是业务规则。

    那么完整性约束呢,这个不难理解。例如输入不能为空,身份证号码必须几位等都是完整性约束。

    业务流程呢,有了这些还不能正常的工作,因为没有启动啊,谁来开启这个业务啊,所以还要有业务流程,例如现在沃尔玛可以在网上购物,我们必须执行:登录--选择商品加入购物车--生成订单--订单确认--付款等一系列操作流程,而这些流程就是我们的业务流。

    其实就是这四部分的构成,但是或许还有些抽象又或者说我们不如真正想看看在业务逻辑层存放哪些代码(这下不抽象了吧 O(∩_∩)O~  ,但是注意业务逻辑和业务逻辑层的区别)。例如现在用户在网上订了一个笔记本,需要生成一个订单。对于数据访问层我们需要在订单这个表中加入一条数据同时写入购物明细到明细表中。业务逻辑层在干什么呢,对于简单的业务逻辑业务逻辑层就是调用数据访问层写入相应的数据表中,但是这个例子显然还不够,在业务逻辑层调用数据访问层的插入操作之后还要执行以短信、email的形式通知用户,如果是支付宝用户还要在用户对于应的支付宝信息上作出通知等,这就是业务逻辑层要做的事情,它是有别于数据访问层和UI层的,数据访问层只知道数据的基本操作而不知道我们在做什么样的开发,因为对它来说你要开发收银系统和银行系统它都是不变的;UI曾呢,它虽然知道要干什么的,但它也仅仅是知道而已,复杂的东西它不管它只做些面子工作实质工作还是由BLL来做。这个例子或者让大家感觉到了业务逻辑层是什么,但还要提醒大家它不等同于业务逻辑,我们上面的图说的是业务逻辑而不是业务逻辑层,业务逻辑除了告诉我们怎么写BLL的代码之外它还有很多作用(例如帮助我们了解整个领域),这一点大家有必要弄清楚。

    业务逻辑谈的也不少了,相信您也有所了解了。最上面一层是表示层,就是负责显示和接受数据用的,我们也不再多说。

三层架构再补充

    上面的三层架构的图或者很常见,但是还没有细化,我们稍微修改一下。

传统的三层架构

    不错,多了两个接口两个工厂以及补全了实体类。为什么这样做呢?两个接口规范我们的DAL和BLL的操作,而且更重要的是它使我们可以针对接口编程和工厂一起起到上层不再直接依赖于下层,而是依赖于下层接口的作用,这就样就进行了层之间的解耦,例如BLL要用到DAL可以用工厂来产生而不必直接new一个DAL实例。不过注意一点在我们没有使用Ioc之前,我们的工厂内部产生DAL接口都是通过new实例化的(注意我们的这个例子中工厂还是和DAL、BLL耦合的,事实上我们希望彻底针对接口使工厂依赖于IDAL和IBLL而不依赖于DAL和BLL,但由于我们在这里还没有学习Ioc所以工厂在构建实例时还是直接new的方式,也就是说 Factory除了依赖于接口外还依赖于具体DAL或BLL,在另一篇博客“Ioc的实现与应用”中我们彻底看看我们的理想状况,到时候我们就彻底做到对象 的创建不在代码中实现而是在外部实现,也就是说将依赖有内部转移到外部就是所谓“依赖注入”)。至于每个DAL或者BLL是否单独对应一个Factory,还是所有的DAL实例都靠一个Factory实例化就要根据情况而定,对于业务不太复杂的系统可以DAL都放到一个Factory创建,BLL也都放到一个Factory中创建,当然如果业务十分简单的话,我们还可能会去掉BLLFactory和IBLL。

    对了,说了那么多我们还没有通过具体的例子来看看我们的架构是如何设计的呢?依然拿我们的登录来举例吧。

    DAL层可以完全采用生成工具进行生成,因为它操作的是元数据,不管你办理什么业务都和它是没有关系的,而BLL层的东西可以部分生成。由于这个例子太过简单所以没有将DAL等放到单独的类库中。目录结构如下图:

传统的三层架构

下面看看各部分的代码(从底层开始):

IDAL中的IUserAccess:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using UI.Entity;

namespace UI.IDAL

{

interface IUserAccess

{

void Insert(User entity);

void Update(User entity);

void Delete(long id);

int GetCountOfAll();

User GetData(int id);

User GetMaxIDOfData();

IList<User> GetEligibleDataList(string sql);

IList<User> GetDataList();

}

}

DAL中的UserAccess:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data;

using Cmj.MyData;

using UI.Entity;

namespace UI.DAL

{

class UserAccess:IDAL.IUserAccess

{

#region 数据库操作辅助类

DataOperate myDataOperate = new DataOperate();

#endregion

#region 向数据库中插入一条新记录。

/// <summary>

/// 向数据库中插入一条新记录。

/// </summary>

/// <param name="entity">实体类</param>

/// <returns>新插入记录的编号</returns>

public void Insert(User entity)

{

//构造字插入符串(可以为Null的属性在插入时可以不赋值)

string _sql = "insert into User(";

if (entity.UserName != null)

{

_sql += "UserName ,";

}

if (entity.UserPassword != null)

{

_sql += "UserPassword ,";

}

_sql = _sql.TrimEnd(',');

_sql += ") values(";

if (entity.UserName != null)

{

_sql += "'" + entity.UserName + "' ,";

}

if (entity.UserPassword != null)

{

_sql += "'" + entity.UserPassword + "' ,";

}

_sql = _sql.TrimEnd(',');

_sql += ")";

//执行插入

myDataOperate.executeCommand(_sql);

}

#endregion

#region 更新表User中指定的一条记录

/// <summary>

/// 更新表User中指定的一条记录

/// </summary>

/// <param name="entity">实体类</param>

public void Update(User entity)

{

//构造字插入符串(允许为NUll的值在修改时可以不给属性赋值)

string _sql = "update User set ";

if (entity.UserName != null)

{

_sql += "UserName='" + entity.UserName + "' ,";

}

if (entity.UserPassword != null)

{

_sql += "UserPassword='" + entity.UserPassword + "' ,";

}

_sql = _sql.TrimEnd(',');

_sql += " where ID=" + entity.ID + "";

//执行插入

myDataOperate.executeCommand(_sql);

}

#endregion

#region 删除数据表User中的一条记录

/// <summary>

/// 删除数据表User中的一条记录

/// </summary>

/// <param name="ID">iD</param>

/// <returns>无</returns>

public void Delete(long id)

{

string _sql = "delete from User where ID=" + id + " ";

myDataOperate.executeCommand(_sql);

}

#endregion

#region 得到 User 记录数目

/// <summary>

/// 得到 User 记录数目

/// </summary>

/// <param>无</param>

/// <returns>记录数目</returns>

public int GetCountOfAll()

{

string _sql = "select count(*) as c from User";

int _count = int.Parse(myDataOperate.getDataTable(_sql).Rows[0]["c"].ToString());

return _count;

}

#endregion

#region 得到 User 数据实体

/// <summary>

/// 得到 User 数据实体

/// </summary>

/// <param name="id">实体id</param>

/// <returns>实体类</returns>

public User GetData(int id)

{

string _sql = "select ";

_sql += "ID ,";

_sql += "UserName ,";

_sql += "UserPassword ";

_sql += " from User where ID=" + id + " ";

DataTable dt = myDataOperate.getDataTable(_sql);

User entity = new User();

entity.ID = ((dt.Rows[0]["ID"]) == DBNull.Value) ? 0 : Convert.ToInt32(dt.Rows[0]["ID"]);

if (dt.Rows[0]["UserName"].ToString() != string.Empty)

{

entity.UserName = dt.Rows[0]["UserName"].ToString();

}

if (dt.Rows[0]["UserPassword"].ToString() != string.Empty)

{

entity.UserPassword = dt.Rows[0]["UserPassword"].ToString();

}

return entity;

}

#endregion

#region 得到 User 中id最大的数据实体

/// <summary>

/// 得到 User 中id最大的数据实体

/// </summary>

/// <returns>实体类</returns>

public User GetMaxIDOfData()

{

string _sql = "select ";

_sql += "ID ,";

_sql += "UserName ,";

_sql += "UserPassword ";

_sql += " from User where ID=(select max(ID) from User) ";

DataTable dt = myDataOperate.getDataTable(_sql);

User entity = new User();

entity.ID = ((dt.Rows[0]["ID"]) == DBNull.Value) ? 0 : Convert.ToInt32(dt.Rows[0]["ID"]);

if (dt.Rows[0]["UserName"].ToString() != string.Empty)

{

entity.UserName = dt.Rows[0]["UserName"].ToString();

}

if (dt.Rows[0]["UserPassword"].ToString() != string.Empty)

{

entity.UserPassword = dt.Rows[0]["UserPassword"].ToString();

}

return entity;

}

#endregion

#region 得到数据表User指定的记录

/// <summary>

/// 得到数据表User指定的记录

/// </summary>

/// <returns>实体类集合</returns>

public IList<User> GetEligibleDataList(string sql)

{

IList<User> entities = new List<User>();

string _sql = sql;

DataTable dt = myDataOperate.getDataTable(_sql);

User entity;

foreach (DataRow dr in dt.Rows)

{

entity = new User();

entity.ID = ((dr["ID"]) == DBNull.Value) ? 0 : Convert.ToInt32(dr["ID"]);

if (dr["UserName"].ToString() != string.Empty)

{

entity.UserName = dr["UserName"].ToString();

}

if (dr["UserPassword"].ToString() != string.Empty)

{

entity.UserPassword = dr["UserPassword"].ToString();

}

entities.Add(entity);

}

return entities;

}

#endregion

#region 得到数据表User所有记录

/// <summary>

/// 得到数据表User所有记录

/// </summary>

/// <returns>实体类集合</returns>

public IList<User> GetDataList()

{

IList<User> entities = new List<User>();

string _sql = "select * from [User]";

DataTable dt = myDataOperate.getDataTable(_sql);

User entity;

foreach (DataRow dr in dt.Rows)

{

entity = new User();

entity.ID = ((dr["ID"]) == DBNull.Value) ? 0 : Convert.ToInt32(dr["ID"]);

if (dr["UserName"].ToString() != string.Empty)

{

entity.UserName = dr["UserName"].ToString();

}

if (dr["UserPassword"].ToString() != string.Empty)

{

entity.UserPassword = dr["UserPassword"].ToString();

}

entities.Add(entity);

}

return entities;

}

#endregion

}

}

DALFactory的UserAccessFactory:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using UI.IDAL;

using UI.DAL;

namespace UI.DALFactory

{

class UserAccessFactory

{

public static IUserAccess CreateUserAccess()

{

return new UserAccess();

}

}

}

IBLL中的IUserLogic:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using UI.Entity;

namespace UI.IBLL

{

interface IUserLogic

{

bool Login(string name, string password);

}

}

BLL中的UserLogic:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using UI.IBLL;

using UI.DALFactory;

using UI.DAL;

using UI.Entity;

namespace UI.BLL

{

class UserLogic:IUserLogic

{

/*

* 下面这个方法被注释掉了,因为在这个登录逻辑中不需要添加用户,

* 我想说的是大家注意我没有用Insert命名,而是用了AddUser

* 第二点我没有在UserLogic中定义一个变量UserAccess uAccess=UserAccessFactory.CreateUserAccess()

* as UserAccess;然后在AddUser中调用uAccess.Inserrt(entity);这样的话工厂就没有作用了,BLL和DAL就耦合了

* 因为很明显BLL中用到了DAL中的类

public void AddUser(User entity)

{

UserAccessFactory.CreateUserAccess().Insert(entity);

}

*/

public bool Login(string name, string password)

{

bool isBeing = false;

foreach (User user in UserAccessFactory.CreateUserAccess().GetDataList())

{

if (user.UserName == name && user.UserPassword == password)

{

isBeing = true;

}

}

return isBeing;

}

}

}

BLLFactory中的UserLogicFactory:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using UI.BLL;

using UI.IBLL;

namespace UI.BLLFactory

{

class UserLogicFactory

{

public static IUserLogic CreateUserLogic()

{

return new UserLogic();

}

}

}

UILogin:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using UI.BLLFactory;

using UI.BLL;

using UI.Entity;

namespace UI

{

public partial class UILogin : Form

{

public UILogin()

{

InitializeComponent();

}

private void btnLogin_Click(object sender, EventArgs e)

{

if (UserLogicFactory.CreateUserLogic().Login(tbUserName.Text,tbUserPassword.Text))

{

UIMain uiMain = new UIMain();

uiMain.Show();

}

else

{

MessageBox.Show("用户名或密码错误!");

}

}

}

}

三层架构的本质我们慢慢体会,但至少看了上面的例子我们知道怎么去利用三层架构进行实际开发。好了,整个三层架构我们说完了,事实上对于业务比较简单的系统有时候我们可能没有BLL层,相反对于十分复杂的系统我们可能还要添加Ioc这点在其他部分我们会讨论这里不再说了。

注意:对于DAL的Access部分是用拼接sql的方式,不建议大家使用,主要是我的另一套codesmith模板没有找到,这是很早以前的模板了。

例子源码下载:UI.rar