模擬場景:
某某公司要求我們做一個商場收銀系統,
提出需求:商場會不定時舉辦一系列的優惠活動,優惠方式暫定爲:打折扣,滿多少還多少(例如:滿300還100)
初步場景分析:
看到這個需求,第一感覺就會潛意識的認爲“這個太簡單了”。
1.商場收銀系統:定義爲winform的應用程序
2.活動優惠的計算,判斷一下就可以了。
初步代碼實現:
- /// <summary>
- /// 點擊確定計算收費
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void btnOk_Click(object sender, EventArgs e)
- {
- int number = Convert.ToInt32(this.txtNumber.Text);
- double price = Convert.ToDouble(this.txtPrice.Text);
- double total = 0;
- switch (this.cbxType.SelectedItem.ToString())
- {
- case "正常收費":
- total = number * price;
- break;
- case "滿300返100":
- moneyCondition = 300;
- moneyReturn = 100;
- double money = Convert.ToDouble(txtPrice.Text) * Convert.ToInt32(txtNumber.Text);
- if (money >= moneyCondition)
- {
- total = money - Math.Floor(money / moneyCondition) * moneyReturn;
- }
- break;
- case "打8折":
- total = Convert.ToDouble(txtPrice.Text) * Convert.ToInt32(txtNumber.Text) * 0.8;
- break;
- }
- this.lbxList.Items.Add("單價:" + txtPrice.Text + "數量:" + txtNumber.Text + " "
- + cbxType.SelectedItem.ToString() + "合計: " + total.ToString());
- this.lblResult.Text = total.ToString();
- }
好,現在我們來分析一下上面的實現:
咋一看,感覺沒什麼問題,可再細分析,問題就多了:
1:顯示和邏輯緊密的聯繫在一起。
2:完全過程式的編程,沒辦法複用。
3:當新需求增加的時候,還需要修改這個條件分支,不符合開放封閉原則,過多的判斷不利於維護。
經過以上分析,進行初步的修改:
先來看看結構圖:
工廠類:
- public class CashFactory
- {
- public static CashSuper createCashAccpet(string type)
- {
- CashSuper cs = null;
- switch (type)
- {
- case "正常收費":
- cs = new CashNormal();
- break;
- case "滿300返100":
- cs = new CashReturn("300","100");
- break;
- case "打8折":
- cs = new CashRebate("0.8");
- break;
- }
- return cs;
- }
- }
運算父類:
- /// <summary>
- /// 現金收取父類,算法的抽象類
- /// </summary>
- public abstract class CashSuper
- {
- /// <summary>
- /// 抽象方法:收取現金
- /// </summary>
- /// <param name="money">原價</param>
- /// <returns>當前價</returns>
- public abstract double acceptCash(double money);
- }
折扣類:
- /// <summary>
- /// 打折收費
- /// </summary>
- public class CashRebate:CashSuper
- {
- private double moneyRebate = 1d;
- public CashRebate(string money)
- {
- this.moneyRebate = double.Parse(money);
- }
- //初始化時必需輸入折扣率,如八折就是0.8
- public override double acceptCash(double money)
- {
- return money * moneyRebate;
- }
- }
返利類:
- /// <summary>
- /// 返利收費
- /// </summary>
- public class CashReturn:CashSuper
- {
- private double moneyCondition = 0.0d;
- private double moneyReturn = 0.0d;
- /// <summary>
- /// 初始化時必須要輸入返利條件和返利值,比如滿300返100,則moneyCondition爲300,moneyReturn爲100
- /// </summary>
- /// <param name="moneyCondition"></param>
- /// <param name="moneyReturn"></param>
- public CashReturn(string moneyCondition,string moneyReturn)
- {
- this.moneyCondition = double.Parse(moneyCondition);
- this.moneyReturn = double.Parse(moneyReturn);
- }
- public override double acceptCash(double money)
- {
- double result = money;
- //若大於返利條件,則需要減去返利值
- if (money>=moneyCondition)
- {
- result = money-Math.Floor(money/moneyCondition)*moneyReturn;
- }
- return result;
- }
- }
正常收費類:
- /// <summary>
- /// 正常收費
- /// </summary>
- public class CashNormal:CashSuper
- {
- public override double acceptCash(double money)
- {
- return money;
- }
- }
好的,經過完善之後我們的代碼已經出來了,下面我們再來細細分析一下這代碼:
代碼使用了簡單工廠模式來解決了對象的創建,通過抽象實現了業務邏輯的分離,在維護性上也得到了改善。
那麼這代碼就沒有問題了嗎?答案是:有
現在假設我們要爲商場添加一種優惠活動,滿100積分送10點,以後積分達到一定值就可以領取獎品。
我們該怎麼去做呢:現在有了工廠,我們只需要添加一個積分的算法類,
讓它繼承運算父類(CashSuper),在再工廠條件分支裏添加一個滿100積分送10點的條件分支,界面再稍稍修改就可以了。
這麼想雖然是可以解決問題,但是工廠裏包含了所有的收費方式,商場是可能經常性的更改打折額度,添加新的優惠方式,
如果是這樣的話,我們每次維護或擴展收費方式都要去改動這個工廠,這樣做就不得不讓代碼重新編譯部署,這樣的處理方式是很糟糕的。
那麼我們該如何去做呢?現在我們就進入正題:其實商場的促銷活動,打折活動,返利活動都是一些算法,而算法本身只是一種策略,
最重要的是這些算法是隨時都可能互相替換的,這就是變化點,而封裝變化點是我們面向對象的一種很重要的思維方式。
我們先來看看策略模式的結構圖:
Strategy類,定義所有支持的算法和公共接口:
ConcreteStrategy封裝了具體的算法或行爲,繼承於Strategy:
Context,用一個ConcreteStrategy來配置,維護一個對Strategy對象的引用:
客戶端的實現:
下面我們就用策略模式來改善一下我們的代碼:
代碼結構圖:
現有的代碼沒有CashContext,我們就添加一個CashContext的類:
其他的類不變,客戶端實現:
經過策略模式完善的代碼已經實現,下面我們再來分析一下我們的代碼:
現在問題又來了,算法的判斷又回到客戶端裏來了,我們該如何去解決這個問題呢?
其實我們可以讓策略模式與簡單工廠模式相結合,下面我們來看看怎麼個實現法:
在CashContext裏進行對象的構造:
客戶端實現:
經過進一步的優化,我們的代碼已經比較的優化了,但是一個很明顯的問題也顯示出來了,就是switch的條件判斷分支,
當我們需要添加一種算法的時候,我們還是需要修改CashContext裏的switch算法,這樣的代碼還是讓人非常的不爽,哪還有什麼方法呢?
我們可以使用反射來改善現有的代碼(反射的具體原理與實現將在不久寫出):
switch算法的條件判斷分支我們抽取出來,用一個xml文件進行記錄:
接下來我們只需要在加載事件中將xml的信息讀取出來綁定就可以了:
由於我們的switch已經轉移到xml中了,那麼我們的CashContext類就可以簡化成:
我們的具體算法實現類不變,現在我們最主要就是通過反射去實例化不同的算法對象:
總結:
代碼經過多次的修改,可維護性,代耦合高內聚的特性已經展現出來了,現在我們再來分析一下上面的代碼吧,上面我們通過反射去除了switch分支,
有效的把耦合降到最低,但是這樣做也是有一點點代價的,反射會比較耗一點性能,所以我們做程序的時候要具體問題具體分析。
通過上面的代碼我們來分析一下策略模式吧:
策略模式是一種定義一系列算法的方法,從概念上來看,所有這些算法完成的都是相同的工作,只是實現不同,它可以以相同的方式調用所有的算法,減少了各種算法類與使用算法類之間的耦合。
優點:
1、策略模式簡化了單元測試,因爲每個算法都有自己的類,可以通過自己的接口單獨測試,每個算法可以保證它沒有錯誤,修改其中任一個時也不會影響其他的算法。
2、策略模式封裝了變化,策略模式就是用來封裝算法的,但在實踐中,我們發現可以用它來封裝幾乎任何類型的規則,
只要在分析過程中聽到需要在不同時間應用不同的業務規則,就可以考慮使用策略模式處理這種變化的可能性。
3、策略模式有利於程序的高內聚、低偶合性,使得程序更加的靈活。
缺點:
基本策略模式中所用具體實現的職責由客戶端對象承擔,並轉給策略模式的Context對象,
這本身並沒有解除客戶端需要選擇判斷的壓力,而策略模式與簡單工廠模式結合後,選擇具體實現的職責也可以由Context來承擔,這就是最大化地減輕了客戶端的職責。
以上內容轉自(盡供本人學習使用):http://blog.csdn.net/shiyuan17/article/details/9056637 。感謝博主花費時間和經歷爲我們準備如此好的學習資料。