接前篇。

       “到底如何去改良策略模式呢?”小菜懇切地問道。

       “你仔細觀察過沒有,你的代碼,不管是用工廠模式寫的,還是用策略模式寫的,那個分支的switch依然去不掉。原因在哪里?”大鳥反問道。

       “因為程序里有下拉選擇,用戶是有選擇的,那么程序就必須要根據用戶的選擇來決定實例化哪一個子類對象。無論是在客戶端窗體類編程還是到工廠類里編程,這個switch總是少不掉的。問題主要出在這里。”小菜十分肯定的說。

       “是呀,”大鳥道,“所以我們要考慮的就是可不可以不在程序里寫明‘如果是打折就去實例化CashRebate類,如果是返利就去實例化CashReturn類’這樣的語句,而是在當用戶做了下拉選擇后,再根據用戶的選擇去某個地方找應該要實例化的類是哪一個。這樣,我們的switch就可以對它說再見了。”

       “聽不太懂哦,什么叫‘去某個地方找應該要實例化的類是哪一個’?”小菜糊涂地說。

       “我要說的就是一種編程方式:依賴注入(Dependency Injection),從字面上不太好理解,我們也不去管它。關鍵在于如何去用這種方法來解決我們的switch問題。本來依賴注入是需要專門的IoC容器提供,比如spring.net,顯然當前這個程序不需要這么麻煩,你只需要再了解一個簡單的.net技術‘反射’就可以了。”

       “大鳥,你一下子說出又是‘依賴注入’又是‘反射’這些莫名其妙的名詞,我有點暈哦!”小菜有些犯困,“我就想知道,如何向switch說bye-bye!至于那些什么概念我不想了解。”

       “心急討不了好媳婦!你急什么?”大鳥嘲笑道,“反射技術看起來很玄乎,其實實際用起來不算難。”

       “請看下面的兩個樣例:

Java代碼
  1. //實例化方法一      
  2. //原來我們把一個類實例化是這樣的   
  3. Animal animal=new Cat();  //聲明一個動物對象,名稱叫animal,然后將animal實例化成貓類的對象   
  4.   
  5. //實例化方法二   
  6. //我們還可以用反射的辦法得到這個實例   
  7. using System.Reflection;//先引用System.Reflection   
  8. //假設當前程序集是AnimalSystem,名稱空間也是AnimalSystem   
  9. Animal animal = (Animal)Assembly.Load("AnimalSystem").CreateInstance("AnimalSystem.Cat");  

       其中關鍵是:

       Assembly.Load("程序集名稱").CreateInstance("名稱空間.類名稱")

       那也就是說,我們可以在實例化的時候,再給計算機一個類的名稱字符串,來讓計算機知道應該實例化哪一個類。”大鳥講解道。

       “你的意思是,我之前寫的‘cc.setBehavior(new CashNormal());’可以改寫為‘cc.setBehavior((CashSuper)Assembly.Load("商場管理軟件").CreateInstance("商場管理軟件.CashNormal")’,不過,這只不過是換了種寫法而已,又有什么神奇之處呢?”小菜依然迷茫。

       “分析一下,原來new CashNormal()是什么?是否是寫死在程序里的代碼,你可以靈活更換嗎?”大鳥問。

       “不可以,那還換什么,寫什么就是什么了唄。”

       “那你說,在反射中的CreateInstance("商場管理軟件.CashNormal"),可以靈活更換‘CashNormal’嗎?”大鳥接著問。

       “還不是一樣,寫死在代碼…………等等,哦!!!我明白了。”小菜一下子頓悟過來,,興奮起來。“因為這里是字符串,可以用變量來處理,也就可以根據需要更換。哦,My God!太妙了!”

      “哈哈,前面有篇博文《小菜和大鳥的編程故事之一:活字印刷-面向對象思想的先驅》中曾經寫過,‘體會到面向對象帶來的好處,那種感覺應該就如同是一中國酒鬼第一次喝到了茅臺,西洋酒鬼第一次喝到了XO一樣,怎個爽字可形容呀。’,你有沒有這種感覺了?”

       “嗯,我一下子知道這里的差別主要在原來的實例化是寫死在程序里的,而現在用了反射就可以利用字符串來實例化對象,而變量是可以
更換的。”小菜說道。

       “由于字符串是可以寫成變量,而變量的值到底是CashReturn(返利),還是CashRebate(打折),完全可以由誰決定?”大鳥再問。

       “當然是由用戶在下拉中選擇的選項決定,也就是說,我只要把下拉選項的值改成這些算法子類的名稱就好了,是吧?”

       “你說得對,不過還不是最好。因為把comboBox的每個選項value都改為算法子類的名稱。以后我們要加子類,你不是還要去改comboBox嗎?繼續往下想,現在我們的代碼對有誰依賴?”

       “對下拉控件comboBox的選項有依賴。”

       “那么怎么辦,這個控件的選項可不可以通過別的方式生成。比如利用它的綁定?”

       “你的意思是讀數據庫?”

       “讀數據庫當然最好了,其實用不著這么麻煩,我們不是有XML這個東東嗎,寫個配置文件不就解決了?”

       “哦,我知道你的意思了,讓它去讀XML的配置文件,來生成這個下拉列表框,然后再根據用戶的選擇,通過反射實時的實例化出相應的算法對象,最終利用策略模式計算最終的結果。好的好的,我馬上去寫出來。我現在真有一種不把程序寫出來就難受的感覺了。”小菜急切的說。

      “OK,還有一個小細節,你的CashRebate和CashReturn在構造函數中都是有參數的,這需要用到CreateInstance()方法的重載函數,不會用去查幫助吧!”

      “好嘞!你別走哦,等我,不見不散!”小菜向外跑著還叫道。

       大鳥搖頭苦笑,嘴里嘟囔著:“這小子,忒急了吧!還不見不散呢,難道真沒完沒了啦!”

       一個小時后,小菜交出了商場收銀程序的第五份作業。

       客戶端主要代碼:

Java代碼
  1.        using System.Reflection;   
  2.   
  3.        DataSet ds;//用于存放配置文件信息   
  4.         double total = 0.0d;//用于總計   
  5.   
  6.         private void Form1_Load(object sender, EventArgs e)   
  7.         {   
  8.             //讀配置文件   
  9.             ds = new DataSet();   
  10.             ds.ReadXml(Application.StartupPath + "\\CashAcceptType.xml");   
  11.             //將讀取到的記錄綁定到下拉列表框中   
  12.             foreach (DataRowView dr in ds.Tables[0].DefaultView)   
  13.             {   
  14.                 cbxType.Items.Add(dr["name"].ToString());   
  15.             }   
  16.             cbxType.SelectedIndex = 0;   
  17.         }   
  18.   
  19.         private void btnOk_Click(object sender, EventArgs e)   
  20.         {   
  21.             CashContext cc = new CashContext();   
  22.             //根據用戶的選項,查詢用戶選擇項的相關行   
  23.             DataRow dr = ((DataRow[])ds.Tables[0].Select("name='" + cbxType.SelectedItem.ToString()+"'"))[0];   
  24.             //聲明一個參數的對象數組   
  25.             object[] args =null;   
  26.             //若有參數,則將其分割成字符串數組,用于實例化時所用的參數   
  27.             if (dr["para"].ToString() != "")   
  28.                 args = dr["para"].ToString().Split(',');   
  29.             //通過反射實例化出相應的算法對象   
  30.             cc.setBehavior((CashSuper)Assembly.Load("商場管理軟件").CreateInstance("商場管理軟件." + dr["class"].ToString(), false, BindingFlags.Default, null, args, nullnull));   
  31.                
  32.             double totalPrices = 0d;   
  33.             totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));   
  34.             total = total + totalPrices;   
  35.             lbxList.Items.Add("單價:" + txtPrice.Text + " 數量:" + txtNum.Text + " "+cbxType.SelectedItem+ " 合計:" + totalPrices.ToString());   
  36.             lblResult.Text = total.ToString();   
  37.         }  

       配置文件 CashAcceptType.xml 的代碼:

XML/HTML代碼
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <CashAcceptType>  
  3.     <type>  
  4.         <name>正常收費</name>  
  5.         <class>CashNormal</class>  
  6.         <para></para>  
  7.     </type>  
  8.     <type>  
  9.         <name>滿300返100</name>  
  10.         <class>CashReturn</class>  
  11.         <para>300,100</para>  
  12.     </type>  
  13.     <type>  
  14.         <name>滿200返50</name>  
  15.         <class>CashReturn</class>  
  16.         <para>200,50</para>  
  17.     </type>  
  18.     <type>  
  19.         <name>打8折</name>  
  20. 20        <class>CashRebate</class>  
  21. 21        <para>0.8</para>  
  22. 22    </type>  
  23. 23    <type>  
  24. 24        <name>打7折</name>  
  25. 25        <class>CashRebate</class>  
  26. 26        <para>0.7</para>  
  27. 27    </type>  
  28. 28</CashAcceptType>  

       實現的界面同之前一樣:

小菜和大鳥的編程故事之九:反射--程序員的快樂

       “大鳥,我再次搞定了,這會是真的明白了。”小菜說。

       “說說看,你現在的理解!”大鳥問。

       “無論你的需求是什么,我現在連程序都不動,只需要去改改XML文件就全部擺平。比如你如果覺得現在滿300送100太多,要改成送80,我只需要去XML文件里改就行,再比如你希望增加新的算法,比如積分返點,那我先寫一個返點的算法類繼承CashSuper,再去改一下XML文件,對過去的代碼依然不動。總之,現在是真的做到了程序易維護,可擴展。”小菜得意地壞笑道,“吼吼!此時商場老板以為要改一天的程序,我幾分鐘就搞定,一天都可以休息。反射——真是程序員的快樂呀!”

       “在做夢了吧,你當老板是傻瓜,會用反射才是正常水平,不會用的早應該走人了。”大鳥打擊了小菜的情緒,“不過呢小菜的確是有長進,不再是小菜鳥了。那你說說看,現在代碼還有沒有問題。”

       “還有不足?不會吧,我都改5次了,重構到了這個地步,還會有什么問題?”小菜不以為然。

       “知足是可以常樂,但知足如何能進步!你的代碼真的沒有問題了,比如說,你現在把列表是打印在了listBox列表框中,我現在還需要輸出到打印機打印成交易單據,我還希望這些清單能存入數據庫中,你需要改客戶端的代碼嗎?”

       “這個,你這是加需求了,更改當然是必須的。”

       “更改是必須的沒有錯,但為什么我只是要對交易清單加打印和存數據,就需要去改客戶端的代碼呢?這兩者沒什么關系吧?”大鳥說。

       “啊,你的意思是…………”

       “別急著下結論,先去好好思考一下再說。”大鳥打斷了小菜。

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