天天看點

一起談.NET技術,走向ASP.NET架構設計——第三章:分層設計,初涉架構(前篇)

  本篇主要講述ASP.NET應用中如何進行邏輯分層。本篇的前篇會從Smart UI 反模式和它的一些缺點開始講述,然後一步步的講述如何邏輯分層,而且在後篇中也會給出一個ASP.NET設計中常用的僅供參考的分層架構的Demo。

  一個穩定和易維護的系統必須建立在一個好的基礎之上。計劃和設計一個好的架構對一個項目的成敗起着至關重要的作用。可能在我們一般做項目的時候,經驗告訴我們:3層,N層的設計,基本就能把問題解決了,很多的情況确實是這樣的。在提出一個設計的時候,常常要考慮為什麼要這樣劃分結構,而且常常要承擔風險和責任,特别是萬一這個項目因為最初的設計而導緻崩潰,那就郁悶了。是以設計的提出一定和考慮業務。

  下面就先來看看Smart UI的設計方式。

  Smart UI

  想想我們最初是如何開發ASP.NET的應用的:在頁面設計界面中把界面布局好,然後輕按兩下控件就開始編寫功能代碼。很多的時候把邏輯判斷和資料通路都寫在頁面的.cs的檔案中。後來我們學習到了分層,逐漸的明白了這種方式的缺點:導緻業務邏輯代碼到處分散而且重複,不利于以後的更改和維護等。

  盡管有上述說的一些缺點,Smart UI還是有它的用途的,如為項目快速的建立一個原型或者開發一個功能比較的小的項目。還有一個問題,如何最初用Smart UI的方式開發的小項目很成功,慢慢的變大,變複雜了,那麼很多的問題就出來了。就像Flower在架構模式一書中提到的:盡量用領域模型來組織一個項目的業務邏輯,盡管在開始的時候邏輯不複雜或者看不出這種方式的好處,一旦項目變化,好處就顯而易見了。在對項目原型開發中,盡量不用Smart UI。

  其實Smart UI最大的問題就是:職責不清—把所有的東西全部寫在一起。為了和以後講述的内容的比較,我還是寫一個例子出來,很多朋友都已經對這種Smart UI的開發方式很熟悉了,可以跳過下面的例子。在例子中,我們會用電子商務中一個常見的場景:一個頁面來顯示一個産品的清單資訊,如名字,推薦的零售價格(Recommend Retail Price),折扣,和庫存等。(如果朋友們願意,可以照着下面的步驟一起做)

  1. 打開Visual Studio,并且建立一個”空白的解決方案”,命名為:ASPPatterns.Chap3.SmartUI,然後添加一個新的Web項目,命名為:ASPPatterns.Chap3.SmartUI.Web.

  2. 在建立的Web項目中右擊:Add—New Item,添加一個Sql Server的資料檔案:Shop.mdf.

  如下:

  3. 在新加的資料庫檔案上右擊,并且打開。然後添加一個新表:如下:

   其中ProductId設定為自動标示。然後儲存為Products表。

  4. 添加一些測試的資料:

  5. 然後選擇Products表,并且把表拖放到Default.aspx頁面上。這樣之後,在頁面上就自動添加一個GridView和SqlDataSource.

  界面就如下圖:

  6. 我我們添加額外的兩列來顯示折扣資訊和庫存資訊。Default.aspx的Source代碼最後如下:

  7. 然後,我們在Default.aspx.cs後編碼:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

namespace ASPPatterns.Chap3.SmartUI.Web

{

public partial class Default : System.Web.UI.Page

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)

if (e.Row.RowType == DataControlRowType.DataRow)

decimal RRP = decimal.Parse(((System.Data.DataRowView)e.Row.DataItem)["RRP"].ToString());

decimal SellingPrice = decimal.Parse(((System.Data.DataRowView)e.Row.DataItem)["SellingPrice"].ToString());

Label lblSellingPrice = (Label)e.Row.FindControl("lblSellingPrice");

Label lblSavings = (Label)e.Row.FindControl("lblSavings");

Label lblDiscount = (Label)e.Row.FindControl("lblDiscount");

lblSavings.Text = DisplaySavings(RRP, ApplyExtraDiscountsTo(SellingPrice));

lblDiscount.Text = DisplayDiscount(RRP, ApplyExtraDiscountsTo(SellingPrice));

lblSellingPrice.Text = String.Format("{0:C}", ApplyExtraDiscountsTo(SellingPrice));

}

protected string DisplayDiscount(decimal RRP, decimal SalePrice)

string discountText = "";

if (RRP > SalePrice)

discountText = String.Format("{0:C}", (RRP - SalePrice));

return discountText;

protected string DisplaySavings(decimal RRP, decimal SalePrice)

string savingsTest = "";

savingsTest = (1 - (SalePrice / RRP)).ToString("#%");

return savingsTest;

protected decimal ApplyExtraDiscountsTo(decimal OriginalSalePrice)

decimal price = OriginalSalePrice;

int discountType = Int16.Parse( this.ddlDiscountType.SelectedValue);

if (discountType == 1)

price = price * 0.95M;

return price;

protected void ddlDiscountType_SelectedIndexChanged(object sender, EventArgs e)

GridView1.DataBind();

  在上面的 GridView1_RowDataBound方法在GridView的每個row被建立的時候調用。這個方法擷取每個産品的推薦的零售價格RRP(Recommend Retail Price),然後調用 DisplayDiscount和DisplaySavings方法來擷取折扣和庫存,然後再更新UI的顯示。

  在上面的代碼中,就将計算折扣和計算庫存的邏輯寫在了UI中,而且資料的通路代碼也寫在UI中了。這就意味着:如果我們想要在不同的頁面顯示産品的資訊,那麼這些邏輯就得一遍遍的重寫。如果我們在加一些新的功能,那麼頁面後面的代碼就開始修改,開始縫縫補補。

  解決Smart UI的方法就是劃分職責,我想大家都知道“單一職責的原則”,這個原則不僅僅适用于類,方法,而且對項目的層次劃分也有作用。分層,最主要的目的就是:把不通的功能放在各自對應的地方,這樣清晰的職責劃分,也是對變化點進行分離。

  下面的圖就是一個典型的企業級ASP.NET項目的分層結構:

  下面我們就來看看,按照我們的一般的分層的經驗來如何設計這功能:

  1. 建立一個新的空白的解決方案,命名為ASPPatterns.Chap3.Layered.

  2. 添加四個新的C#類庫,分别命名為:

  a) ASPPatterns.Chap3.Layered.Repository.

  b) ASPPatterns.Chap3.Layered.Model.

  c) ASPPatterns.Chap3.Layered.Service.

  d) ASPPatterns.Chap3.Layered.Presentation

  3. 添加一個新的Web程式,命名為ASPPatterns.Chap3.Layered.Web.。

  注:朋友們一眼就應該可以看出,這些類庫的命名是反映了一些DDD的一些概念,但是,不是說在一個項目的開發中用了這些概念名詞就表明就開發的方式是DDD了。

  3. 不同的類庫就分别承擔不同的職責,而且每一層一般都隻是引用自己的下一層,而且下一層不知道自己被上一層使用。本例中的引用關系如下:

  這裡我先提一下上面類庫的一起名字:盡管有關DDD和一些架構模式的概念我在以後的文章中會講,但我這裡還是先給大家提一下,目的僅僅是讓大家對這個例子有一些更好的了解。

  在DDD中,一直主張業務模型,也就是我們常常所說的業務類,例如之前例子中的Product,隻關注自身的業務邏輯,而不管如何去擷取和儲存資料,這些對資料的操作完全交給另外的對象去執行,也就是Repository,這樣就達到了DDD中所說的PI(Persistence Ignore)。是以在上面的例子中,ASPPatterns.Chap3.Layered.Model就代表了一個業務模型,它之是以被Repository引用,是因為Repository負責将Model的資料持久化到儲存設備中,而Model不管這些事情了。

  在講ASPPatterns.Chap3.Layered.Service之前,首先給大家統一 一下Service的概念。 

  有時在類的設計過程中,有些行為不适合放在任何的一個類中,如果把這些行為放在一個不真正擁有它的類中,隻能把類的職責搞混了。為了給這些行為一個安置的地方,我們常常把這些行為放在一個稱為服務的類中。

  作為服務的類一般沒有狀态的,可以簡單的作為一個提供操作接口實作。

  在DDD中,Service也是用來提供一種服務的。很多人看到了DDD的類層次結構是這樣的:Repository---Model---Service--- Presentation(包括本例),是以都以為Service隻能出現在Model的上一層,如果看到Repository-- Service ---Model---Service--- Presentation這樣的層次結構,又作何感想。如果被這些所謂的結構搞迷惑了,那就說明對DDD的了解隻是在于“形”上。Service就是向外部提供的功能接口,和我們常見的Web Service的概念很相似,例如的Web Service就是向外部系統提供一些功能的。

  我們來看下面的一個圖:

一起談.NET技術,走向ASP.NET架構設計——第三章:分層設計,初涉架構(前篇)

  有時候之是以要在Model層之上加上一個Service層,主要的原因就是實作粗顆粒度的API,往往和系統的User Case有一定的聯系。例如,如果在系統用例中要實作一個使用者訂單的處理,那麼可能就涉及到Customer, Product,Order等類,當然,如果我們調用這些類來共同完成這個任務是沒問題的,但是這樣就向調用者暴露這些類之間的複雜的關系,而且如果處理的過程變化了,那麼調用者的代碼就要改變,如果把這個處理的方法放在上面的任意一個類中,又顯得不倫不類,這裡的Service功能就類似于設計模式中的Façade外觀模式。這樣就向外界提供簡單的API,向外界提供訂單處理的服務!

  是以在一般在DDD中業務層被劃分為兩個邏輯層:Model (提供細粒度的業務邏輯處理,也便于重用), Service(提供業務處理的流程,提供粗顆粒度的供外部調用的方法)。

  但是,我們常見到的Model層之上的Service層僅僅隻是對CRUD的再次封裝,一個可能的原因就是業務不是很複雜,這時其實這個Service層可以拿掉的,但是考慮到以後可能邏輯會更多更複雜,是以還是保留Service這層。

  其實在Repository上的那個Service也是同樣的概念。例如發送郵件通知使用者的功能。例如上圖中的最上層的Service可以調用業務層和基礎設施層的Service來共同完成一個事情。

  今天的上篇的就講述到這裡吧,下篇會用一個例子,代碼量還是有點的!敬請關注!

繼續閱讀