策略模式(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         }

这样,此案例使用策略模式实现就完成了。

细心的小伙伴可能看出了此案例中策略模式也有很多弊端,原来的简单工厂模式客户端去判断用哪个算法,但是需要频繁的修改工厂类,而策略模式很好的解决了工厂模式的弊端,但是需要在客户端判断用哪个算法,唉,难道鱼和熊掌不能兼得吗?

答案是可以的,我们可以用简单工厂模式结合策略模式来完成上述案例,那么就可以得到完美的效果。下一篇,将会为大家讲述如何用简单工厂+策略模式来解决我们的问题


本系列将持续更新,喜欢的小伙伴可以点一下关注和推荐,谢谢大家的支持。

posted @ 2017-01-03 10:59  萌萌丶小魔王  阅读(519)  评论(0编辑  收藏  举报