zoukankan      html  css  js  c++  java
  • 策略模式(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         }

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

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

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


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

  • 相关阅读:
    使用curl传递cookie错误的问题
    P3P设置第三方cookie解决方案
    国内的在线编程学习网站
    十大在线编程学习网站
    CGI,FastCGI,PHP-CGI与PHP-FPM
    工具收藏
    初识docker
    PHP优化
    JS各种语法
    面试题 比较运算、去扩展名的多种方法、内存回收
  • 原文地址:https://www.cnblogs.com/xiaomowang/p/6236935.html
Copyright © 2011-2022 走看看