“三層結(jié)構(gòu)”是什么?
“三層結(jié)構(gòu)”一詞中的“三層”是指:“表現(xiàn)層”、“中間業(yè)務(wù)層”、“數(shù)據(jù)訪問層”。其中:
n 表 現(xiàn) 層:位于最外層(最上層),離用戶最近。用于顯示數(shù)據(jù)和接收用戶輸入的數(shù)據(jù),為用戶提供一種交互式操作的界面。
n 中間業(yè)務(wù)層:負(fù)責(zé)處理用戶輸入的信息,或者是將這些信息發(fā)送給數(shù)據(jù)訪問層進(jìn)行保存,或者是調(diào)用數(shù)據(jù)訪問層中的函數(shù)再次讀出這些數(shù)據(jù)。中間業(yè)務(wù)層也可以包括一些對“商業(yè)邏輯”描述代碼在里面。
n 數(shù)據(jù)訪問層:僅實(shí)現(xiàn)對數(shù)據(jù)的保存和讀取操作。數(shù)據(jù)訪問,可以訪問數(shù)據(jù)庫系統(tǒng)、二進(jìn)制文件、文本文檔或是XML文檔。
對依賴方向的研究將是本文的重點(diǎn),數(shù)值返回方向基本上是沒有變化的。
在一個
如果只以分層的設(shè)計角度看,Duwamish7要比PetShop3.0復(fù)雜一些!而如果較為全面的比較二者,PetShop3.0則顯得比較復(fù)雜。但我們先不討論這些,對PetShop3.0和Duwamish7的研究,并不是本文的重點(diǎn)?,F(xiàn)在的問題就是:既然“三層結(jié)構(gòu)”已經(jīng)被分派到各自的項目中,那么剩下來的項目是做什么的呢?例如PetShop3.0中的“Model”、“IDAL”、“DALFactory”這三個項目,再例如Duwamish7中的“Common”項目,還有就是在Bincess.CN彬月論壇中的“Classes”、“DbTask”、這兩個項目。它們究竟是做什么用的呢?
對“三層結(jié)構(gòu)”的深入理解——從一家小餐館說起
一個“三層結(jié)構(gòu)”的Web應(yīng)用程序,就好象是一家小餐館。
n 表 現(xiàn) 層,所有的.aspx頁面就好像是這家餐館的菜譜。
n 中間業(yè)務(wù)層,就像是餐館的服務(wù)生。
n 數(shù)據(jù)訪問層,就像是餐館的大廚師傅。
n 而我們這些網(wǎng)站瀏覽者,就是去餐館吃飯的吃客了……
我們?nèi)ヒ患也宛^吃飯,首先得看他們的菜譜,然后喚來服務(wù)生,告訴他我們想要吃的菜肴。服務(wù)生記下來以后,便會馬上去通知大廚師傅要烹制這些菜。大廚師傅收到通知后,馬上起火燒菜。過了不久,服務(wù)生便把一道一道香噴噴的、熱氣騰騰的美味端到我們的桌位上——
而我們訪問一個基于asp.net技術(shù)的網(wǎng)站的時候,首先打開的是一個aspx頁面。這個aspx頁面的后臺程序會去調(diào)用中間業(yè)務(wù)層的相應(yīng)函數(shù)來獲取結(jié)果。中間業(yè)務(wù)層又會去調(diào)用數(shù)據(jù)訪問層的相應(yīng)函數(shù)來獲取結(jié)果。
為什么需要“三層結(jié)構(gòu)”?——初探,就從數(shù)據(jù)庫的升遷開始
一個站點(diǎn)中,訪問數(shù)據(jù)庫的程序代碼散落在各個頁面中,就像夜空中的星星一樣繁多。這樣一動百動的維護(hù),難度可想而知。最難以忍受的是,對這種維護(hù)工作的投入,是沒有任何價值的……
有一個比較好的解決辦法,那就是將訪問數(shù)據(jù)庫的代碼全部都放在一個程序文件里。這樣,數(shù)據(jù)庫平臺一旦發(fā)生變化,那么只需要集中修改這一個文件就可以了。我想有點(diǎn)開發(fā)經(jīng)驗的人,都會想到這一步的。這種“以不變應(yīng)萬變”的做法其實(shí)是簡單的“門面模式”的應(yīng)用。如果把一個網(wǎng)站比喻成一家大飯店,那么“門面模式”中的“門面”,就像是飯店的服務(wù)生,而一個網(wǎng)站的瀏覽者,就像是一個來賓。來賓只需要發(fā)送命令給服務(wù)生,然后服務(wù)生就會按照命令辦事。至于服務(wù)生經(jīng)歷了多少辛苦才把事情辦成?那個并不是來賓感興趣的事情,來賓們只要求服務(wù)生盡快把自己交待事情辦完。我們就把ListLWord.aspx.cs程序就看成是一個來賓發(fā)出的命令,而把新加入的LWordTask.cs程序看成是一個飯店服務(wù)生,那么來賓發(fā)出的命令就是:
“給我讀出留言板數(shù)據(jù)庫中的數(shù)據(jù),填充到DataSet數(shù)據(jù)集中并顯示出來!”
而服務(wù)生接到命令后,就會依照執(zhí)行。而PostLWord.aspx.cs程序,讓服務(wù)生做的是:
“把我的留言內(nèi)容寫入到數(shù)據(jù)庫中!”
而服務(wù)生接到命令后,就會依照執(zhí)行。這就是TraceLWord2!可以在CodePackage/TraceLWord2目錄中找到——
把所有的有關(guān)數(shù)據(jù)訪問的代碼都放到LWordTask.cs文件里,LWordTask.cs程序文件如下:
#001 using System;
#002 using System.Data;
#003 using System.Data.OleDb; // 需要操作 Access 數(shù)據(jù)庫
#004 using System.Web;
#005
#006 namespace TraceLWord2
#007 {
#008 /// <summary>
#009 /// LWordTask 數(shù)據(jù)庫任務(wù)類
#010 /// </summary>
#011 public class LWordTask
#012 {
#013 // 數(shù)據(jù)庫連接字符串
#014 private const string DB_CONN=@"PROVIDER=Microsoft.Jet.OLEDB.4.0;
DATA Source=C:\DbFs\TraceLWordDb.mdb";
#015
#016 /// <summary>
#017 /// 讀取數(shù)據(jù)庫表 LWord,并填充 DataSet 數(shù)據(jù)集
#018 /// </summary>
#019 /// <param name="ds">填充目標(biāo)數(shù)據(jù)集</param>
#020 /// <param name="tableName">表名稱</param>
#021 /// <returns>記錄行數(shù)</returns>
#022 public int ListLWord(DataSet ds, string tableName)
#023 {
#024 string cmdText="SELECT * FROM [LWord] ORDER BY [LWordID] DESC";
#025
#026 OleDbConnection dbConn=new OleDbConnection(DB_CONN);
#027 OleDbDataAdapter dbAdp=new OleDbDataAdapter(cmdText, dbConn);
#028
#029 int count=dbAdp.Fill(ds, tableName);
#030
#031 return count;
#032 }
#033
#034 /// <summary>
#035 /// 發(fā)送留言信息到數(shù)據(jù)庫
#036 /// </summary>
#037 /// <param name="textContent">留言內(nèi)容</param>
#038 public void PostLWord(string textContent)
#039 {
#040 // 留言內(nèi)容不能為空
#041 if(textContent==null || textContent=="")
#042 throw new Exception("留言內(nèi)容為空");
#043
#044 string cmdText="INSERT INTO [LWord]([TextContent]) VALUES(@TextContent)";
#045
#046 OleDbConnection dbConn=new OleDbConnection(DB_CONN);
#047 OleDbCommand dbCmd=new OleDbCommand(cmdText, dbConn);
#048
#049 // 設(shè)置留言內(nèi)容
#050 dbCmd.Parameters.Add(new OleDbParameter("@TextContent", OleDbType.LongVarWChar));
#051 dbCmd.Parameters["@TextContent"].Value=textContent;
#052
#053 try
#054 {
#055 dbConn.Open();
#056 dbCmd.ExecuteNonQuery();
#057 }
#058 catch
#059 {
#060 throw;
#061 }
#062 finally
#063 {
#064 dbConn.Close();
#065 }
#066 }
#067 }
#068 }
如果將數(shù)據(jù)庫從Access 2000修改為SQL Server 2000,那么只需要修改LWordTask.cs這一個文件。如果LWordTask.cs文件太大,也可以把它切割成幾個文件或“類”。如果被切割成的“類”還是很多,也可以把這些訪問數(shù)據(jù)庫的類放到一個新建的“項目”里。當(dāng)然,原來的ListLWord.aspx.cs文件應(yīng)該作以修改,LWord_DataBind函數(shù)被修改成:
...
#046 private void LWord_DataBind()
#047 {
#048 DataSet ds=new DataSet();
#049 (new LWordTask()).ListLWord(ds, @"LWordTable");
#050
#051 m_lwordListCtrl.DataSource=ds.Tables[@"LWordTable"].DefaultView;
#052 m_lwordListCtrl.DataBind();
#053 }
...
原來的PostLWord.aspx.cs文件也應(yīng)作以修改,Post_ServerClick函數(shù)被修改成:
...
#048 private void Post_ServerClick(object sender, EventArgs e)
#049 {
#050 // 獲取留言內(nèi)容
#051 string textContent=this.m_txtContent.Value;
#052
#053 (new LWordTask()).PostLWord(textContent);
#054
#055 // 跳轉(zhuǎn)到留言顯示頁面
#056 Response.Redirect("ListLWord.aspx", true);
#057 }
...
從前面的程序段中可以看出,ListLWord.aspx.cs和PostLWord.aspx.cs這兩個文件已經(jīng)找不到和數(shù)據(jù)庫相關(guān)的代碼了。只看到一些和LWordTask類有關(guān)系的代碼,這就符合了“設(shè)計模式”中的一種重要原則:“迪米特法則”。“迪米特法則”主要是說:讓一個“類”與盡量少的其它的類發(fā)生關(guān)系。在TraceLWord1中,ListLWord.aspx.cs這個類和OleDbConnection及OleDbDataAdapter都發(fā)生了關(guān)系,所以它破壞了“迪米特法則”。利用一個“中間人”是“迪米特法則”解決問題的辦法,這也是“門面模式”必須遵循的原則。下面就引出這個LWordTask門面類的示意圖:
ListLWord.aspx.cs和PostLWord.aspx.cs兩個文件對數(shù)據(jù)庫的訪問,全部委托LWordTask類這個“中間人”來辦理。利用“門面模式”,將頁面類和數(shù)據(jù)庫類進(jìn)行隔離。這樣就作到了頁面類不依賴于數(shù)據(jù)庫的效果。以一段比較簡單的代碼來描述這三個程序的關(guān)系:
public class ListLWord
{
private void LWord_DataBind()
{
(new LWordTask()).ListLWord( ... );
}
}
public class PostLWord
{
private void Post_ServerClick(object sender, EventArgs e)
{
(new LWordTask()).PostLWord( ... );
}
}
public class LWordTask
{
public DataSet ListLWord(DataSet ds)...
public void PostLWord(string textContent)...
}
應(yīng)用中間業(yè)務(wù)層,實(shí)現(xiàn)“三層結(jié)構(gòu)”
前面這種分離數(shù)據(jù)訪問代碼的形式,可以說是一種“三層結(jié)構(gòu)”的簡化形式。因為它沒有“中間業(yè)務(wù)層”也可以稱呼它為“二層結(jié)構(gòu)”。一個真正的“三層”程序,是要有“中間業(yè)務(wù)層”的,而它的作用是連接“外觀層”和“數(shù)據(jù)訪問層”。換句話說:“外觀層”的任務(wù)先委托給“中間業(yè)務(wù)層”來辦理,然后“中間業(yè)務(wù)層”再去委托“數(shù)據(jù)訪問層”來辦理……
那么為什么要應(yīng)用“中間業(yè)務(wù)層”呢?“中間業(yè)務(wù)層”的用途有很多,例如:驗證用戶輸入數(shù)據(jù)、緩存從數(shù)據(jù)庫中讀取的數(shù)據(jù)等等……但是,“中間業(yè)務(wù)層”的實(shí)際目的是將“數(shù)據(jù)訪問層”的最基礎(chǔ)的存儲邏輯組合起來,形成一種業(yè)務(wù)規(guī)則。例如:“在一個購物網(wǎng)站中有這樣的一個規(guī)則:在該網(wǎng)站第一次購物的用戶,系統(tǒng)為其自動注冊”。這樣的業(yè)務(wù)邏輯放在中間層最合適:
在“數(shù)據(jù)訪問層”中,最好不要出現(xiàn)任何“業(yè)務(wù)邏輯”!也就是說,要保證“數(shù)據(jù)訪問層”的中的函數(shù)功能的原子性!即最小性和不可再分。“數(shù)據(jù)訪問層”只管負(fù)責(zé)存儲或讀取數(shù)據(jù)就可以了。
在新TraceLWord3中,應(yīng)用了“企業(yè)級模板項目”。把原來的LWordTask.cs,并放置到一個單一的項目里,項目名稱為:AccessTask。解決方案中又新建了一個名稱為:InterService的項目,該項目中包含一個LWordService.cs程序文件,它便是“中間業(yè)務(wù)層”程序。
ASP.NET Web應(yīng)用程序解決方案中,并不是說有aspx文件、有dll文件、還有數(shù)據(jù)庫,就是“三層結(jié)構(gòu)”的Web應(yīng)用程序,這樣的說法是不對的。也并不是說沒有對數(shù)據(jù)庫進(jìn)行操作,就不是“三層結(jié)構(gòu)”的。其實(shí)“三層結(jié)構(gòu)”是功能實(shí)現(xiàn)上的三層。例如,在微軟的ASP.NET示范實(shí)例“Duwamish7”中,“表現(xiàn)層”被放置在“Web”項目中,“中間業(yè)務(wù)層”是放置在“BusinessFacade”項目中,“數(shù)據(jù)訪問層”則是放置在“DataAccess”項目中……而在微軟的另一個ASP.NET示范實(shí)例“PetShop3.0”中,“表現(xiàn)層”被放置在“Web”項目中,“中間業(yè)務(wù)層”是放置在“BLL”項目中,而“數(shù)據(jù)訪問層”則是放置在“SQLServerDAL”和“OracleDAL”兩個項目中。在Bincess.CN彬月論壇中,“表現(xiàn)層”是被放置在“WebForum”項目中,“中間業(yè)務(wù)(服務(wù))層”是被放置在“InterService”項目中,而“數(shù)據(jù)訪問層”是被放置在“SqlServerTask”項目中。