對“三層結構”的深入理解——怎樣纔算是一個符合“三層結構”的Web應用程序?

對“三層結構”的深入理解——怎樣纔算是一個符合“三層結構”的Web應用程序?

ASP.NET Web應用程序解決方案中,並不是說有aspx文件、有dll文件、還有數據庫,就是“三層結構”的Web應用程序,這樣的說法是不對的。也並不是說沒有對數據庫進行操作,就不是“三層結構”的。其實“三層結構”是功能實現上的三層。例如,在微軟的ASP.NET示範實例“Duwamish7中,“表現層”被放置在“Web”項目中,“中間業務層”是放置在“BusinessFacade”項目中,“數據訪問層”則是放置在“DataAccess”項目中……而在微軟的另一個ASP.NET示範實例“PetShop3.0中,“表現層”被放置在“Web”項目中,“中間業務層”是放置在“BLL”項目中,而“數據訪問層”則是放置在“SQLServerDAL”和“OracleDAL”兩個項目中。Bincess.CN彬月論壇中,“表現層”是被放置在“WebForum”項目中,“中間業務(服務)層”是被放置在“InterService”項目中,而“數據訪問層”是被放置在“SqlServerTask”項目中。

 

“三層結構”是什麼?

  “三層結構”一詞中的“三層”是指:“表現層”、“中間業務層”、“數據訪問層”。其中:

n            表 現 層:位於最外層(最上層),離用戶最近。用於顯示數據和接收用戶輸入的數據,爲用戶提供一種交互式操作的界面。

n            中間業務層:負責處理用戶輸入的信息,或者是將這些信息發送給數據訪問層進行保存,或者是調用數據訪問層中的函數再次讀出這些數據。中間業務層也可以包括一些對“商業邏輯”描述代碼在裏面。

n            數據訪問層:僅實現對數據的保存和讀取操作。數據訪問,可以訪問數據庫系統、二進制文件、文本文檔或是XML文檔。

  

 

  對依賴方向的研究將是本文的重點,數值返回方向基本上是沒有變化的。


在一個

  如果只以分層的設計角度看,Duwamish7要比PetShop3.0複雜一些!而如果較爲全面的比較二者,PetShop3.0則顯得比較複雜。但我們先不討論這些,對PetShop3.0Duwamish7的研究,並不是本文的重點。現在的問題就是:既然“三層結構”已經被分派到各自的項目中,那麼剩下來的項目是做什麼的呢?例如PetShop3.0中的“Model”、“IDAL”、“DALFactory”這三個項目,再例如Duwamish7中的“Common”項目,還有就是在Bincess.CN彬月論壇中的“Classes”、“DbTask”、這兩個項目。它們究竟是做什麼用的呢?

 

對“三層結構”的深入理解——從一家小餐館說起

  一個“三層結構”的Web應用程序,就好象是一家小餐館。

n            表 現 層,所有的.aspx頁面就好像是這家餐館的菜譜。

n            中間業務層,就像是餐館的服務生。

n            數據訪問層,就像是餐館的大廚師傅。

n            而我們這些網站瀏覽者,就是去餐館吃飯的吃客了……

 

 

我們去一家餐館吃飯,首先得看他們的菜譜,然後喚來服務生,告訴他我們想要吃的菜餚。服務生記下來以後,便會馬上去通知大廚師傅要烹製這些菜。大廚師傅收到通知後,馬上起火燒菜。過了不久,服務生便把一道一道香噴噴的、熱氣騰騰的美味端到我們的桌位上——

而我們訪問一個基於asp.net技術的網站的時候,首先打開的是一個aspx頁面。這個aspx頁面的後臺程序會去調用中間業務層的相應函數來獲取結果。中間業務層又會去調用數據訪問層的相應函數來獲取結果。 

爲什麼需要“三層結構”?——初探,就從數據庫的升遷開始

一個站點中,訪問數據庫的程序代碼散落在各個頁面中,就像夜空中的星星一樣繁多。這樣一動百動的維護,難度可想而知。最難以忍受的是,對這種維護工作的投入,是沒有任何價值的……

有一個比較好的解決辦法,那就是將訪問數據庫的代碼全部都放在一個程序文件裏。這樣,數據庫平臺一旦發生變化,那麼只需要集中修改這一個文件就可以了。我想有點開發經驗的人,都會想到這一步的。這種“以不變應萬變”的做法其實是簡單的“門面模式”的應用。如果把一個網站比喻成一家大飯店,那麼“門面模式”中的“門面”,就像是飯店的服務生,而一個網站的瀏覽者,就像是一個來賓。來賓只需要發送命令給服務生,然後服務生就會按照命令辦事。至於服務生經歷了多少辛苦才把事情辦成?那個並不是來賓感興趣的事情,來賓們只要求服務生儘快把自己交待事情辦完。我們就把ListLWord.aspx.cs程序就看成是一個來賓發出的命令,而把新加入的LWordTask.cs程序看成是一個飯店服務生,那麼來賓發出的命令就是:

“給我讀出留言板數據庫中的數據,填充到DataSet數據集中並顯示出來!”

而服務生接到命令後,就會依照執行。而PostLWord.aspx.cs程序,讓服務生做的是:

“把我的留言內容寫入到數據庫中!”

而服務生接到命令後,就會依照執行。這就是TraceLWord2!可以在CodePackage/TraceLWord2目錄中找到——

 

把所有的有關數據訪問的代碼都放到LWordTask.cs文件裏,LWordTask.cs程序文件如下:

 

#001 using System;

#002 using System.Data;

#003 using System.Data.OleDb;   // 需要操作 Access 數據庫

#004 using System.Web;

#005

#006 namespace TraceLWord2

#007 {

#008    /// <summary>

#009    /// LWordTask 數據庫任務類

#010    /// </summary>

#011    public class LWordTask

#012    {

#013        // 數據庫連接字符串

#014        private const string DB_CONN=@"PROVIDER=Microsoft.Jet.OLEDB.4.0;

DATA Source=C:/DbFs/TraceLWordDb.mdb";

#015

#016        /// <summary>

#017        /// 讀取數據庫表 LWord,並填充 DataSet 數據集

#018        /// </summary>

#019        /// <param name="ds">填充目標數據集</param>

#020        /// <param name="tableName">表名稱</param>

#021        /// <returns>記錄行數</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        /// 發送留言信息到數據庫

#036        /// </summary>

#037        /// <param name="textContent">留言內容</param>

#038        public void PostLWord(string textContent)

#039        {

#040            // 留言內容不能爲空

#041            if(textContent==null || textContent=="")

#042                throw new Exception("留言內容爲空");

#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            // 設置留言內容

#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 }

 

如果將數據庫從Access 2000修改爲SQL Server 2000,那麼只需要修改LWordTask.cs這一個文件。如果LWordTask.cs文件太大,也可以把它切割成幾個文件或“類”。如果被切割成的“類”還是很多,也可以把這些訪問數據庫的類放到一個新建的“項目”裏。當然,原來的ListLWord.aspx.cs文件應該作以修改,LWord_DataBind函數被修改成:

 

...

#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文件也應作以修改,Post_ServerClick函數被修改成:

 

...

#048        private void Post_ServerClick(object sender, EventArgs e)

#049        {

#050            // 獲取留言內容

#051            string textContent=this.m_txtContent.Value;

#052

#053            (new LWordTask()).PostLWord(textContent);

#054

#055            // 跳轉到留言顯示頁面

#056            Response.Redirect("ListLWord.aspx", true);

#057        }

...

 

  從前面的程序段中可以看出,ListLWord.aspx.csPostLWord.aspx.cs這兩個文件已經找不到和數據庫相關的代碼了。只看到一些和LWordTask類有關係的代碼,這就符合了“設計模式”中的一種重要原則:“迪米特法則”。“迪米特法則”主要是說:讓一個“類”與儘量少的其它的類發生關係。TraceLWord1中,ListLWord.aspx.cs這個類和OleDbConnectionOleDbDataAdapter都發生了關係,所以它破壞了“迪米特法則”。利用一個“中間人”是“迪米特法則”解決問題的辦法,這也是“門面模式”必須遵循的原則。下面就引出這個LWordTask門面類的示意圖:

 

 

ListLWord.aspx.csPostLWord.aspx.cs兩個文件對數據庫的訪問,全部委託LWordTask類這個“中間人”來辦理。利用“門面模式”,將頁面類和數據庫類進行隔離。這樣就作到了頁面類不依賴於數據庫的效果。以一段比較簡單的代碼來描述這三個程序的關係:

 

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)...

}

 


應用中間業務層,實現“三層結構”

前面這種分離數據訪問代碼的形式,可以說是一種“三層結構”的簡化形式。因爲它沒有“中間業務層”也可以稱呼它爲“二層結構”。一個真正的“三層”程序,是要有“中間業務層”的,而它的作用是連接“外觀層”和“數據訪問層”。換句話說:“外觀層”的任務先委託給“中間業務層”來辦理,然後“中間業務層”再去委託“數據訪問層”來辦理……

那麼爲什麼要應用“中間業務層”呢?“中間業務層”的用途有很多,例如:驗證用戶輸入數據、緩存從數據庫中讀取的數據等等……但是,“中間業務層”的實際目的是將“數據訪問層”的最基礎的存儲邏輯組合起來,形成一種業務規則。例如:“在一個購物網站中有這樣的一個規則:在該網站第一次購物的用戶,系統爲其自動註冊”。這樣的業務邏輯放在中間層最合適:

 

 

在“數據訪問層”中,最好不要出現任何“業務邏輯”!也就是說,要保證“數據訪問層”的中的函數功能的原子性!即最小性和不可再分。“數據訪問層”只管負責存儲或讀取數據就可以了。

  在新TraceLWord3中,應用了“企業級模板項目”。把原來的LWordTask.cs,並放置到一個單一的項目裏,項目名稱爲:AccessTask。解決方案中又新建了一個名稱爲:InterService的項目,該項目中包含一個LWordService.cs程序文件,它便是“中間業務層”程序。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章