大鳥說道:“實際上沒有學過設計模式去理解三層架構會有失偏頗的,畢竟分層是更高一級別的模式,所謂的架構模式。不過在程序中,有意識的遵循設計原則,卻也可以有效的做出好的設計。”

      “不要告訴我,剛才講的‘迪米特法則’就會在分層中用得上?”小菜說。

     “當然用得上,否則講它干嗎,你當我是在安慰你而臨時編個法則來騙騙你呀?來,再來看看你上次寫的代碼。”

C#代碼
  1. private void Form1_Load(object sender, EventArgs e)   
  2. {   
  3.      //讀配置文件   
  4.      ds = new DataSet();   
  5.      ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml");   
  6.      //將讀取到的記錄綁定到下拉列表框中   
  7.      foreach (DataRowView dr in ds.Tables[0].DefaultView)   
  8.      {   
  9.           cbxType.Items.Add(dr["name"].ToString());   
  10.      }   
  11.      cbxType.SelectedIndex = 0;   
  12. }  

       “這是Form_Load的代碼,里面有沒有什么與界面無關的東西?”大鳥問道。

       “第4、5行是讀配置文件的代碼,它應該屬于DAL層。對吧?”

       “很好,再看下面的這段,里面又有哪些呢?”

C#代碼
  1. private void btnOk_Click(object sender, EventArgs e)   
  2. {   
  3.     CashContext cc = new CashContext();   
  4.     //根據用戶的選項,查詢用戶選擇項的相關行   
  5.     DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + cbxType.SelectedItem.ToString()+"'"))[0];   
  6.     //聲明一個參數的對象數組   
  7.     object[] args =null;   
  8.     //若有參數,則將其分割成字符串數組,用于實例化時所用的參數   
  9.     if (dr["para"].ToString() != "")   
  10.         args = dr["para"].ToString().Split(',');   
  11.     //通過反射實例化出相應的算法對象   
  12.     cc.setBehavior((CashSuper)Assembly.Load("商場管理軟件").CreateInstance("商場管理軟件." + dr["class"].ToString(),    
  13.                         false, BindingFlags.Default, null, args, nullnull));   
  14.        
  15.     double totalPrices = 0d;   
  16.     totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));   
  17.     total = total + totalPrices;   
  18.     lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合計:" + totalPrices.ToString());   
  19.     lblResult.Text = total.ToString();   
  20. }  

       “這里3-13行,是為確定哪種算法而創建CashContext對象,其中用到了反射技術,為計算做準備。第16行是真正的計算打折價或返利,17-19是界面顯示的部分。所以應該把3-16行都搬到BLL層去。不過,我還有些疑問,這樣做會讓配置文件的數據要先從DAL轉到BLL,再轉到表示層,多麻煩呀,什么不直接表示層讀DAL,它想要數據就去讀DAL,它想算結果就去請求BLL處理?”

       “那是說明你沒有真的了解什么叫迪米特法則,象你那樣說,不就等于,你小菜又要認識小張,又要認識小李了,這不就耦合過度嗎?本來你只需要認識一個人就可以了,這樣依賴才會小呀!”

       “可是我就得在BLL里寫一個專門返回從DAL里得到數據的方法,這個方法不屬于現在的任何類,我就還得再寫一個類來做這種傳聲筒的角色。而且由于界面還要涉及到其它的類,如CashContext,感覺UI和BLL耦合還是很高。”

       “說得沒錯,你的確是講到點子上了,由于表示層UI需要與BLL有兩個類進行交互,這是很麻煩,不過前輩們就想了了一個較好的辦法,另一個設計模式,‘門面模式’(Facade)或叫外觀模式”。

       門面模式要求一個子系統的外部與其內部的通信必須通過一個統一的門面(Facade)對象進行。門面模式提供一個高層次的接口,使得子系統更易于使用。

       門面模式的結構

       門面模式是對象的結構模式。門面模式沒有一個一般化的類圖描述,下圖演示了一個門面模式的示意性對象圖:

 

       在這個對象圖中,出現了兩個角色:

       門面(Facade)角色:客戶端可以調用這個角色的方法。此角色知曉相關的(一個或者多個)子系統的功能和責任。在正常情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去。

       子系統(subsystem)角色:可以同時有一個或者多個子系統。每一個子系統都不是一個單獨的類,而是一個類的集合。每一個子系統都可以被客戶端直接調用,或者被門面角色調用。子系統并不知道門面的存在,對于子系統而言,門面僅僅是另外一個客戶端而已。

       “哦,你這樣一講,我就明白了。”小菜說,“上篇所講的IT部,其實可以由部門主管就是門面,我們只需要找到部門主管,就可以通過他安排相關的人來提供服務,我們不需要了解IT部的具體情況了。”

       “其實現實中這樣的例子很多。比如以前上海市沒有新聞發言人,當要到春運時,所有的記者都跑到交通部去了解信息,當有非典或禽流感時,所有的記者又跑到衛生部去打聽情況,突然這時候樓市大跌,記者們又得馬不停蹄前往建設部收集新聞。辛苦呀,有什么辦法呢,吃這口飯的。但其實辛苦地又何止只是記者。各個政府部門都需要專人來應付這些記者,不能多說話,不能說錯話,但也不能不說話。也辛苦呀,誰叫他們是政府呢。”大鳥仿佛自己感同身受似的描述著,“于是,新聞發言人橫空出世,一位知識女性焦揚,代表上海市政府發言,從此,老記們不需要頭頂驕陽奔跑于各大政府部門之間,只需要天天等在新聞發言廳門口守著就可以寫出準確及時的新聞。而政府部門也不用專人來應付老記們的圍追堵截,有更多的時間為人民做實事辦好事。這里就只辛苦一個人。”

      “那一定是新聞發言人自己了,因為她需要先與政府部門溝通好,要說些什么、如何說、如何回答刁鉆問題。然后要站在鎂光燈下承受壓力接受記者的訪問。不過,干這一行就是需要辛苦的,這是政府的門面呀。”小菜感慨到。

 

       “好了,去改寫吧,你一定會感受到分層后代碼的漂亮。”大鳥鼓勵道。

         過一小時后,小菜給出商場收銀程序的第六份作業。

         DAL層代碼(目前是讀配置文件,以后可以很容易的修改為訪問數據庫)

C#代碼
  1. using System;   
  2. using System.Collections.Generic;   
  3. using System.Text;   
  4. using System.Data;   
  5.   
  6. namespace 商場管理軟件.DAL   
  7. {   
  8.     public class CashAcceptType   
  9.     {   
  10.         public DataSet GetCashAcceptType()   
  11.         {   
  12.             //讀配置文件到DataSet   
  13.             DataSet ds = new DataSet();   
  14.             ds.ReadXml("CashAcceptType.xml");   
  15.             return ds;   
  16.         }   
  17.     }   
  18. }  

       BLL層主要代碼(Facade類代碼)

C#代碼
  1. namespace 商場管理軟件.BLL   
  2. {   
  3.     public class CashFacade   
  4.     {   
  5.         const string ASSEMBLY_NAME = "商場管理軟件.BLL";   
  6.            
  7.         //得到現金收取類型列表,返回字符串數組   
  8.         public string[] GetCashAcceptTypeList()   
  9.         {   
  10.             CashAcceptType cat = new CashAcceptType();   
  11.             DataSet ds = cat.GetCashAcceptType();   
  12.             int rowCount = ds.Tables[0].DefaultView.Count;   
  13.             string[] arrarResult = new string[rowCount];   
  14.   
  15.             for (int i = 0; i < rowCount; i++)   
  16.             {   
  17.                 arrarResult[i] = (string)ds.Tables[0].DefaultView[i]["name"];   
  18.             }   
  19.             return arrarResult;   
  20.         }   
  21.   
  22.         /// <summary>   
  23.         /// 用于根據商品活動的不同和原價格,計算此商品的實際收費   
  24.         /// </summary>   
  25.         /// <param name="selectValue">下拉列表選擇的折價類型</param>   
  26.         /// <param name="startTotal">原價</param>   
  27.         /// <returns>實際價格</returns>   
  28.         public double GetFactTotal(string selectValue, double startTotal)   
  29.         {   
  30.             CashAcceptType cat = new CashAcceptType();   
  31.             DataSet ds = cat.GetCashAcceptType();   
  32.   
  33.             CashContext cc = new CashContext();   
  34.             DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + selectValue + "'"))[0];   
  35.             object[] args = null;   
  36.             if (dr["para"].ToString() != "")   
  37.                 args = dr["para"].ToString().Split(',');   
  38.   
  39.             cc.setBehavior((CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME + "."    
  40.                                                            + dr["class"].ToString(), false, BindingFlags.Default, null, args, nullnull));   
  41.             return cc.GetResult(startTotal);   
  42.   
  43.         }   
  44.     }   
  45. }  

       UI層代碼(可以很容易的轉換為Web頁面)

C#代碼
  1.  double total = 0.0d;//用于總計   
  2.  CashFacade cf = new CashFacade();   
  3.         
  4.  private void Form1_Load(object sender, EventArgs e)   
  5.  {   
  6.      //讀數據綁定下拉列表   
  7.      cbxType.DataSource=cf.GetCashAcceptTypeList();   
  8.         
  9.      cbxType.SelectedIndex = 0;   
  10.  }   
  11.   
  12.  private void btnOk_Click(object sender, EventArgs e)   
  13.  {   
  14.      double totalPrices = 0d;   
  15.      //傳進下拉選擇值和原價,計算實際收費結果   
  16.      totalPrices = cf.GetFactTotal(cbxType.SelectedItem.ToString(), Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));   
  17.      total = total + totalPrices;   
  18.      lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合計:" + totalPrices.ToString());   
  19.      lblResult.Text = total.ToString();   
  20.  }  

       項目文件結構圖

       “大鳥,來看看這下怎么樣,還有沒有可修改的地方?”小菜問道。

       “小菜開始謙虛了嗎!以前不是一直信誓旦旦,現在怎么,沒信心了?”

       “越學越覺得自己知道的少,感覺代碼重構沒有最好,只有更好呀。”小菜誠心的答道。

       “寫得很不錯。BLL層的CashFacade類其實就是新聞發言人,程序的門面;而應用程序或Web其實就類似CCTV和SMG,都是新聞單位,他們不應該也不需要關心門面后面的實現是如何的。,現在用了門面模式以后,耦合比以前要少很多了,更改會更加方便,擴展也很容易了。你要是再回過頭來看看最初的代碼和現在的代碼,你會體會更深刻,更加明白重構的魅力。”

       大鳥接著說:“之前的代碼,下拉控件的綁定是硬編碼,所以只要改動需求就得改代碼,現在是讀配置文件,大大增加靈活性;之前的代碼是根據用戶選擇,分支判斷執行相應的算法,現在整個算法類全部搬走,做到了業務與界面的分離;之前的代碼由于全寫在form里,所以要更換成Web方式,即C/S改為B/S非常困難,要全部重新寫(注意真實的軟件系統不會這么簡單,所以簡單復制不能解決問題),現在的代碼由于把業務運算分離,所以界面的更改不會影響業務的編寫。還有就是現在的代碼由于DAL與BLL分離,配置文件可以很容易的更換為數據庫讀取,且不需要影響表示層與業務邏輯層的代碼。總的來講,若是程序不會變化,原有的設計就沒什么問題,運行結果正確足夠了,但若是程序可能會時常隨業務而變化,新的設計就大大提高了應變性,這其實就是應用設計模式的目的所在。”

       “我現在越來越有信心學好它,設計模式真的很有意思,學它不學它,寫出來的代碼大不一樣。老大,跟你混,看來沒有錯。”

       “嗨,小菜,我不做老大已經很久了!”大鳥仰身長嘆,揚長而去。

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.ojizl5.fun/software/338.html
2013年9月8日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:16