小菜編程成長記(十三 有了門面,程式員的程式會更加體面。)
(續上篇)
大鳥說道:“實際上沒有學過設計模式去理解三層架構會有失偏頗的,畢竟分層是更高一層級的模式,所謂的架構模式。不過在程式中,有意識的遵循設計原則,卻也可以有效做出好的設計。”
“不要告訴我,剛才講的‘迪米特法則’就會在分層中用得上。”小菜說。
“當然用得上,否則講它幹嗎,你當我是在安慰你而臨時編個法則來騙騙你呀。來,再來看看你上次寫的代碼。”
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層。對吧。”
“很好,再看下面的這段,裡面又有哪些呢。”
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, null, null));
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層代碼(目前是讀設定檔,以後可以很容易的修改為訪問資料庫)
using System;using System.Collections.Generic;using System.Text;using System.Data;namespace 商場管理軟體.DAL{ public class CashAcceptType { public DataSet GetCashAcceptType() { //讀設定檔到DataSet DataSet ds = new DataSet(); ds.ReadXml("CashAcceptType.xml"); return ds; } }}
BLL層主要代碼(Facade類代碼)
namespace 商場管理軟體.BLL{ public class CashFacade { const string ASSEMBLY_NAME = "商場管理軟體.BLL"; //得到現金收取類型列表,返回字串數組 public string[] GetCashAcceptTypeList() { CashAcceptType cat = new CashAcceptType(); DataSet ds = cat.GetCashAcceptType(); int rowCount = ds.Tables[0].DefaultView.Count; string[] arrarResult = new string[rowCount]; for (int i = 0; i < rowCount; i++) { arrarResult[i] = (string)ds.Tables[0].DefaultView[i]["name"]; } return arrarResult; } /// <summary> /// 用於根據商品活動的不同和原價格,計算此商品的實際收費 /// </summary> /// <param name="selectValue">下拉式清單選擇的折價類型</param> /// <param name="startTotal">原價</param> /// <returns>實際價格</returns> public double GetFactTotal(string selectValue, double startTotal) { CashAcceptType cat = new CashAcceptType(); DataSet ds = cat.GetCashAcceptType(); CashContext cc = new CashContext(); DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + selectValue + "'"))[0]; object[] args = null; if (dr["para"].ToString() != "") args = dr["para"].ToString().Split(','); cc.setBehavior((CashSuper)Assembly.Load(ASSEMBLY_NAME).CreateInstance(ASSEMBLY_NAME + "." + dr["class"].ToString(), false, BindingFlags.Default, null, args, null, null)); return cc.GetResult(startTotal); } }}
UI層代碼(可以很容易的轉換為Web頁面)
double total = 0.0d;//用於總計 CashFacade cf = new CashFacade(); private void Form1_Load(object sender, EventArgs e) { //讀資料繫結下拉式清單 cbxType.DataSource=cf.GetCashAcceptTypeList(); cbxType.SelectedIndex = 0; } private void btnOk_Click(object sender, EventArgs e) { double totalPrices = 0d; //傳進下拉選擇值和原價,計算實際收費結果 totalPrices = cf.GetFactTotal(cbxType.SelectedItem.ToString(), Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text)); total = total + totalPrices; lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合計:" + totalPrices.ToString()); lblResult.Text = total.ToString(); }
專案檔結構圖
“大鳥,來看看這下怎麼樣,還有沒有可修改的地方。”小菜問道。
“小菜開始謙虛了嗎。以前不是一直信誓旦旦,現在怎麼,沒信心了。”
“越學越覺得自己知道的少,感覺代碼重構沒有最好,只有更好呀。”小菜誠心的答道。
“寫得很不錯。BLL層的CashFacade類其實就是新聞發言人,程式的門面;而應用程式或Web其實就類似CCTV和SMG,都是新聞單位,他們不應該也不需要關心門面後面的實現是如何的。,現在用了門面模式以後,耦合比以前要少很多了,更改會更加方便,擴充也很容易了。你要是再回過頭來看看最初的代碼和現在的代碼,你會體會更深刻,更加明白重構的魅力。”
大鳥接著說:“之前的代碼,下拉控制項的綁定是寫入程式碼,所以只要改動需求就得改代碼,現在是讀設定檔,大大增加靈活性;之前的代碼是根據使用者選擇,分支判斷執行相應的演算法,現在整個演算法類全部搬走,做到了業務與介面的分離;之前的代碼由於全寫在form裡,所以要更換成Web方式,即C/S改為B/S非常困難,要全部重新寫(注意真實的軟體系統不會這麼簡單,所以簡單複製不能解決問題),現在的代碼由於把業務運算分離,所以介面的更改不會影響業務的編寫。還有就是現在的代碼由於DAL與BLL分離,設定檔可以很容易的更換為資料庫讀取,且不需要影響展示層與商務邏輯層的代碼。總的來講,若是程式不會變化,原有的設計就沒什麼問題,運行結果正確足夠了,但若是程式可能會時常隨業務而變化,新的設計就大大提高了應變性,這其實就是應用設計模式的目的所在。”
“我現在越來越有信心學好它,設計模式真的很有意思,學它不學它,寫出來的代碼大不一樣。老大,跟你混,看來沒有錯。”
“嗨,小菜,我不做老大已經很久了。”大鳥仰身長歎,揚長而去。
(待續)
本文原始碼。其中分四個項目,DAL、BLL、WebUI和WinUI,可設定WebUI和WinUI為啟動項目,注意由於只是學習原始碼,配置路徑沒有做處理(實際應用需要config檔案),WebUI設定檔CashAcceptType.xml在“\商場管理軟體06分層\”根目錄下,而WinUI的設定檔CashAcceptType.xml在“\商場管理軟體06分層\商場管理軟體\bin\Debug\”目錄下。
出處:http://www.cnblogs.com/cj723/archive/2007/03/28/689955.html