這是一個商場收費軟體的一個案例,如下: 用Winform做一個非常簡單的商場計算價格的工具,一般我們寫的代碼和界面如下: 界面: 代碼: 執行效果: 二、演繹 1、第一步演繹 ①商場搞活動,所有商品八折出售。 有的小伙伴直接將原來計算總價的代碼改成下麵的代碼: 額,如果商場不打折了,還需要將這段代碼 ...
這是一個商場收費軟體的一個案例,如下:
用Winform做一個非常簡單的商場計算價格的工具,一般我們寫的代碼和界面如下:
界面:
代碼:
1 /// <summary> 2 /// 點擊確定按鈕 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void OK_Click(object sender, EventArgs e) 7 { 8 double totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text); 9 total = total + totalPrices; 10 listTotal.Items.Add($"單價:{UnitPrice.Text} 數量:{Count.Text} 合計:{totalPrices}"); 11 }
執行效果:
二、演繹
1、第一步演繹
①商場搞活動,所有商品八折出售。
有的小伙伴直接將原來計算總價的代碼改成下麵的代碼:
1 double totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text)*0.8;
額,如果商場不打折了,還需要將這段代碼改回去,如果不是打八折,而是打六折,七折呢。這是不是有點作呢.....
所以,又有小伙伴做瞭如下修改,在界面上增加一個選擇的下拉框,用來選擇打幾折。
界面如下:
新增代碼:
1 /// <summary> 2 /// 窗體載入事件 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void Form1_Load(object sender, EventArgs e) 7 { 8 cobEvent.Items.AddRange(new object[] { "正常收費", "打八折", "打七折", "打五折" }); 9 cobEvent.SelectedIndex = 0; 10 }
有了打折方式的選擇,那麼,確定按鈕事件就這麼寫了:
1 /// <summary> 2 /// 點擊確定按鈕 3 /// </summary> 4 /// <param name="sender"></param> 5 /// <param name="e"></param> 6 private void OK_Click(object sender, EventArgs e) 7 { 8 double totalPrices = 0d; 9 switch (cobEvent.SelectedIndex) 10 { 11 case 0: 12 totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text); 13 break; 14 case 1: 15 totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text)*0.8; 16 break; 17 case 2: 18 totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text)*0.7; 19 break; 20 case 3: 21 totalPrices = Convert.ToDouble(UnitPrice.Text) * Convert.ToDouble(Count.Text)*0.5; 22 break; 23 } 24 25 total = total + totalPrices; 26 listTotal.Items.Add($"單價:{UnitPrice.Text} 數量:{Count.Text} 合計:{totalPrices}"); 27 }
2、第二步演繹
分析上述代碼,發現有幾個問題
①重覆的代碼較多,比如:光 Convert.ToDouble這樣的代碼就寫了有8遍之多。
②如果想加入別的促銷活動(不是打折了),比如:滿300減50這樣的活動,那麼上述代碼顯然就不行了。
針對上述兩個問題,可能大家已經想到了一個比較熟悉的解決辦法,對,那就是上一篇中講到的 簡單工廠 設計模式。
好,那麼,我們把上述需求用簡單工廠模式寫一遍,順便增強一下簡單工廠模式的熟練度。
1 /// <summary> 2 /// 收費抽象類 3 /// </summary> 4 abstract class CashSuper 5 { 6 /// <summary> 7 /// 8 /// </summary> 9 /// <param name="money">原價</param> 10 /// <returns>當前價</returns> 11 public abstract double acceptCash(double money); 12 } 13 /// <summary> 14 /// 正常收費 15 /// </summary> 16 class CashNormal : CashSuper 17 { 18 public override double acceptCash(double money) 19 { 20 return money; 21 } 22 } 23 /// <summary> 24 /// 打折收費 25 /// </summary> 26 class CashRebate : CashSuper 27 { 28 private double moneyRebate = 1d; 29 public CashRebate(string moneyRebate) 30 { 31 this.moneyRebate = double.Parse(moneyRebate); 32 } 33 public override double acceptCash(double money) 34 { 35 return money * moneyRebate; 36 } 37 } 38 /// <summary> 39 /// 返利收費 40 /// </summary> 41 class CashReturn : CashSuper 42 { 43 private double moneyCondition = 0d; 44 private double moneyReturn = 0d; 45 public CashReturn(string moneyCondition, string moneyReturn) 46 { 47 this.moneyCondition = double.Parse(moneyCondition); 48 this.moneyReturn = double.Parse(moneyReturn); 49 } 50 public override double acceptCash(double money) 51 { 52 double result = money; 53 if (money >= moneyCondition) 54 { 55 result = money - Math.Floor(money / moneyCondition) * moneyReturn; 56 } 57 return result; 58 } 59 } 60 /// <summary> 61 /// 收費工廠 62 /// </summary> 63 class CashFactory 64 { 65 public static CashSuper createCashAccept(string type) 66 { 67 CashSuper cs = null; 68 switch (type) 69 { 70 case "正常收費": 71 cs = new CashNormal(); 72 break; 73 case "滿300返100": 74 cs = new CashReturn("300", "100"); 75 break; 76 case "打8折": 77 cs = new CashRebate("0.8"); 78 break; 79 } 80 return cs; 81 } 82 }
好,剩下的就是客戶端調用了,在此就不再寫代碼了。
3、第三步演繹
簡單工廠解決了上述的不少問題,那麼我們會發現簡單工廠模式在這個案例中有個弊端。
①工廠包括了所有的收費方式,但商場會經常性的改變打折額度和返利額度,那麼,我們會非常頻繁的維護CashFactory這個工廠類,然後重新編譯部署。非常麻煩,那麼有什麼好的解決方案嗎?
策略模式很好的解決了上述問題,那麼,我們來看一下策略模式是如何巧妙的解決上述問題的。
在針對上述案例之前,我們先來看一個簡單的策略模式的例子,讓大家循序漸進的瞭解策略模式。
1 /// <summary> 2 /// 定義所有支持的演算法的公共介面 3 /// </summary> 4 abstract class Strategy 5 { 6 public abstract void AlgorithmInterface(); 7 } 8 /// <summary> 9 /// 具體演算法A 10 /// </summary> 11 class ConcreteStrategyA : Strategy 12 { 13 /// <summary> 14 /// 演算法A實現方法 15 /// </summary> 16 public override void AlgorithmInterface() 17 { 18 Console.WriteLine("演算法A實現"); 19 } 20 } 21 /// <summary> 22 /// 具體演算法B 23 /// </summary> 24 class ConcreteStrategyB : Strategy 25 { 26 /// <summary> 27 /// 演算法B實現方法 28 /// </summary> 29 public override void AlgorithmInterface() 30 { 31 Console.WriteLine("演算法B實現"); 32 } 33 } 34 /// <summary> 35 /// 具體演算法C 36 /// </summary> 37 class ConcreteStrategyC : Strategy 38 { 39 /// <summary> 40 /// 演算法C實現方法 41 /// </summary> 42 public override void AlgorithmInterface() 43 { 44 Console.WriteLine("演算法C實現"); 45 } 46 } 47 /// <summary> 48 /// 上下文 49 /// </summary> 50 class Context 51 { 52 Strategy strategy; 53 public Context(Strategy strategy) 54 { 55 //初始化時,傳入具體的策略對象 56 this.strategy = strategy; 57 } 58 //上下文介面 59 public void ContextInterface() 60 { 61 //根據具體策略對象,調用其演算法的方法 62 strategy.AlgorithmInterface(); 63 } 64 }
看一下客戶端如何調用
1 static void Main(string[] args) 2 { 3 Context context; 4 //由於實例化不同的策略,所以最終在調用 context.ContextInterface()時,所獲得的結果就不盡相同 5 context = new Context(new ConcreteStrategyA()); 6 context.ContextInterface(); 7 context = new Context(new ConcreteStrategyB()); 8 context.ContextInterface(); 9 context = new Context(new ConcreteStrategyC()); 10 context.ContextInterface(); 11 Console.ReadKey(); 12 }
上述就是策略模式的一個模版,商場收費這個案例,運用上策略模式那麼,CashSuper類就好比是抽象策略類,那幾種收費模式的類,就好比三個具體的策略,也就是策略模式中的具體演算法。
那麼,我們就將此案例從簡單工廠模式改成策略模式
此案例中,將CashFactory工廠 改為 策略模式中的 Context 上下文,然後客戶端調用再改一下,其他不用變,即可實現從簡單工廠變為策略模式。
1 /// <summary> 2 /// 收費工廠 3 /// </summary> 4 //class CashFactory 5 //{ 6 // public static CashSuper createCashAccept(string type) 7 // { 8 // CashSuper cs = null; 9 // switch (type) 10 // { 11 // case "正常收費": 12 // cs = new CashNormal(); 13 // break; 14 // case "滿300返100": 15 // cs = new CashReturn("300", "100"); 16 // break; 17 // case "打8折": 18 // cs = new CashRebate("0.8"); 19 // break; 20 // } 21 // return cs; 22 // } 23 //} 24 class CashContext 25 { 26 private CashSuper cs; 27 /// <summary> 28 /// 通過構造函數,傳入具體的收費策略 29 /// </summary> 30 /// <param name="csuper"></param> 31 public CashContext(CashSuper csuper) 32 { 33 this.cs = csuper; 34 } 35 /// <summary> 36 /// 根據具體的策略不同,獲取計算結果 37 /// </summary> 38 /// <param name="money"></param> 39 /// <returns></returns> 40 public double GetResult(double money) 41 { 42 return cs.acceptCash(money); 43 } 44 }
客戶端調用:
1 double total = 0.0d; 2 /// <summary> 3 /// 點擊確定按鈕 4 /// </summary> 5 /// <param name="sender"></param> 6 /// <param name="e"></param> 7 private void OK_Click(object sender, EventArgs e) 8 { 9 CashContext cc = null; 10 switch (cobEvent.SelectedItem.ToString()) 11 { 12 case "正常收費": 13 cc = new CashContext(new CashNormal()); 14 break; 15 case "滿300返100": 16 cc = new CashContext(new CashReturn("300", "100")); 17 break; 18 case "打8折": 19 cc = new CashContext(new CashRebate("0.8")); 20 break; 21 } 22 double totalPrices = 0d; 23 totalPrices = cc.GetResult(Convert.ToDouble(UnitPrice.Text)*Convert.ToDouble(Count.Text)); 24 total = total + totalPrices; 25 listTotal.Items.Add($"單價:{UnitPrice.Text} 數量:{Count.Text} 優惠方式:{cobEvent.SelectedItem} 合計:{totalPrices.ToString()}"); 26 } 27 /// <summary> 28 /// 窗體載入事件 29 /// </summary> 30 /// <param name="sender"></param> 31 /// <param name="e"></param> 32 private void Form1_Load(object sender, EventArgs e) 33 { 34 cobEvent.Items.AddRange(new object[] { "正常收費", "滿300返100", "打8折" }); 35 cobEvent.SelectedIndex = 0; 36 }
這樣,此案例使用策略模式實現就完成了。
細心的小伙伴可能看出了此案例中策略模式也有很多弊端,原來的簡單工廠模式客戶端去判斷用哪個演算法,但是需要頻繁的修改工廠類,而策略模式很好的解決了工廠模式的弊端,但是需要在客戶端判斷用哪個演算法,唉,難道魚和熊掌不能兼得嗎?
答案是可以的,我們可以用簡單工廠模式結合策略模式來完成上述案例,那麼就可以得到完美的效果。下一篇,將會為大家講述如何用簡單工廠+策略模式來解決我們的問題。
本系列將持續更新,喜歡的小伙伴可以點一下關註和推薦,謝謝大家的支持。