策略模式(3)
这是一个商场收费软件的一个案例,如下:
用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 }
这样,此案例使用策略模式实现就完成了。
细心的小伙伴可能看出了此案例中策略模式也有很多弊端,原来的简单工厂模式客户端去判断用哪个算法,但是需要频繁的修改工厂类,而策略模式很好的解决了工厂模式的弊端,但是需要在客户端判断用哪个算法,唉,难道鱼和熊掌不能兼得吗?
答案是可以的,我们可以用简单工厂模式结合策略模式来完成上述案例,那么就可以得到完美的效果。下一篇,将会为大家讲述如何用简单工厂+策略模式来解决我们的问题。
本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持。