三層架構
三層架構可以說已經是一個比較成熟的分層架構模型,但是還是有部分人弄不清三層架構是怎麼回事。就我認識的人之中,有一個被大家認為學的不錯的一個同學竟然有一天問老師什麼是三層架構。不是說這個學學的怎麼樣,但至少他自己有時間也可以自己稍微學習一下架構方面的東西。當然我估計不懂的人還不止是他。先來看看我們再熟悉不過的一張圖:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcuQWZyVWehxUZlJHaU9CXPZWUqt2SOpWL1dTciBXU1pXVIZzQuFUbm9laxgkZz0kY1lWO1hWTR1EOflVdXZEWk1iUTR0TQVkbjh0SykWVycDe40WdrBTZTdER25kb0ImVWVjc4QFdSFWSRlGWXNXNwFTevwVbvNmLlJ3b0NXZslmZlZXas5SdsJmLhNWe3ZjZvw1LcpDc0RHaiojIsJye.png)
類似的圖估計大家見過很多,但是我們還是一起來看看吧。從底層開始,資料庫應該是包括很多種的小到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