• 设计模式之策略模式


    模拟场景:

    某某公司要求我们做一个商场收银系统,

    提出需求:商场会不定时举办一系列的优惠活动,优惠方式暂定为:打折扣,满多少还多少(例如:满300还100)

    初步场景分析:

    看到这个需求,第一感觉就会潜意识的认为“这个太简单了”。

    1.商场收银系统:定义为winform的应用程序

    2.活动优惠的计算,判断一下就可以了。

    初步代码实现:

    [csharp] view plain copy
     print?
    1. /// <summary>  
    2.        /// 点击确定计算收费  
    3.        /// </summary>  
    4.        /// <param name="sender"></param>  
    5.        /// <param name="e"></param>  
    6.        private void btnOk_Click(object sender, EventArgs e)  
    7.        {  
    8.            int number = Convert.ToInt32(this.txtNumber.Text);  
    9.            double price = Convert.ToDouble(this.txtPrice.Text);  
    10.            double total = 0;  
    11.            switch (this.cbxType.SelectedItem.ToString())  
    12.            {  
    13.                case "正常收费":  
    14.                    total = number * price;  
    15.                    break;  
    16.                case "满300返100":  
    17.                    moneyCondition = 300;  
    18.                    moneyReturn = 100;  
    19.                    double money = Convert.ToDouble(txtPrice.Text) * Convert.ToInt32(txtNumber.Text);  
    20.                    if (money >= moneyCondition)  
    21.                    {  
    22.                        total = money - Math.Floor(money / moneyCondition) * moneyReturn;  
    23.                    }  
    24.                    break;  
    25.                case "打8折":  
    26.                    total = Convert.ToDouble(txtPrice.Text) * Convert.ToInt32(txtNumber.Text) * 0.8;  
    27.                    break;  
    28.            }  
    29.            this.lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNumber.Text + " "  
    30.                + cbxType.SelectedItem.ToString() + "合计: " + total.ToString());  
    31.            this.lblResult.Text = total.ToString();  
    32.   
    33.        }  


    好,现在我们来分析一下上面的实现:

    咋一看,感觉没什么问题,可再细分析,问题就多了:

    1:显示和逻辑紧密的联系在一起。

    2:完全过程式的编程,没办法复用。

    3:当新需求增加的时候,还需要修改这个条件分支,不符合开放封闭原则,过多的判断不利于维护。

    经过以上分析,进行初步的修改:

    先来看看结构图:

     

    工厂类:

    [csharp] view plain copy
     print?
    1. public class CashFactory  
    2.     {  
    3.         public static CashSuper createCashAccpet(string type)  
    4.         {  
    5.             CashSuper cs = null;  
    6.             switch (type)  
    7.             {  
    8.                 case "正常收费":  
    9.                     cs = new CashNormal();  
    10.                     break;  
    11.                 case "满300返100":  
    12.                     cs = new CashReturn("300","100");  
    13.                     break;  
    14.                 case "打8折":  
    15.                     cs = new CashRebate("0.8");  
    16.                     break;  
    17.             }  
    18.             return cs;  
    19.         }  
    20.     }  

    运算父类:

    [csharp] view plain copy
     print?
    1. /// <summary>  
    2.     /// 现金收取父类,算法的抽象类  
    3.     /// </summary>  
    4.     public 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.     }  


    折扣类:

    [csharp] view plain copy
     print?
    1.  /// <summary>  
    2. /// 打折收费  
    3. /// </summary>  
    4. public class CashRebate:CashSuper  
    5. {  
    6.     private double moneyRebate = 1d;  
    7.   
    8.     public CashRebate(string money)  
    9.     {  
    10.         this.moneyRebate = double.Parse(money);  
    11.     }  
    12.   
    13.     //初始化时必需输入折扣率,如八折就是0.8  
    14.     public override double acceptCash(double money)  
    15.     {  
    16.         return money * moneyRebate;  
    17.     }  
    18. }  


    返利类:

    [csharp] view plain copy
     print?
    1. /// <summary>  
    2. /// 返利收费  
    3. /// </summary>  
    4. public class CashReturn:CashSuper  
    5. {  
    6.     private double moneyCondition = 0.0d;  
    7.     private double moneyReturn = 0.0d;  
    8.   
    9.     /// <summary>  
    10.     /// 初始化时必须要输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100  
    11.     /// </summary>  
    12.     /// <param name="moneyCondition"></param>  
    13.     /// <param name="moneyReturn"></param>  
    14.     public CashReturn(string moneyCondition,string moneyReturn)  
    15.     {  
    16.         this.moneyCondition = double.Parse(moneyCondition);  
    17.         this.moneyReturn = double.Parse(moneyReturn);  
    18.     }  
    19.   
    20.     public override double acceptCash(double money)  
    21.     {  
    22.         double result = money;  
    23.         //若大于返利条件,则需要减去返利值  
    24.         if (money>=moneyCondition)  
    25.         {  
    26.             result = money-Math.Floor(money/moneyCondition)*moneyReturn;  
    27.         }  
    28.         return result;  
    29.     }  
    30. }  


    正常收费类:

    [csharp] view plain copy
     print?
    1.  /// <summary>  
    2. /// 正常收费  
    3. /// </summary>  
    4. public class CashNormal:CashSuper  
    5. {  
    6.     public override double acceptCash(double money)  
    7.     {  
    8.         return money;  
    9.     }  
    10. }  


    好的,经过完善之后我们的代码已经出来了,下面我们再来细细分析一下这代码:

    代码使用了简单工厂模式来解决了对象的创建,通过抽象实现了业务逻辑的分离,在维护性上也得到了改善。

    那么这代码就没有问题了吗?答案是:有

    现在假设我们要为商场添加一种优惠活动,满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、 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
    2、 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
    3、 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
     

    优点:

    1、策略模式简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试,每个算法可以保证它没有错误,修改其中任一个时也不会影响其他的算法。

    2、策略模式封装了变化,策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,
    只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

    3、策略模式有利于程序的高内聚、低偶合性,使得程序更加的灵活。

    缺点:

    基本策略模式中所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象,
    这本身并没有解除客户端需要选择判断的压力,而策略模式与简单工厂模式结合后,选择具体实现的职责也可以由Context来承担,这就是最大化地减轻了客户端的职责。

     原文:http://blog.csdn.net/shiyuan17/article/details/9056637

     ----------------------------------------

    在下面的情况下应当考虑使用策略模式:
    1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
    2. 一个系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体算法类,并只持有一个数据类型是抽象算法类的对象。
    3. 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。
    4. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设计的概念。

     策略模式的优点和缺点
    策略模式有很多优点和缺点。它的优点有:
    1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码。
    2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
    3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
    策略模式的缺点有:
    1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
    2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

    策略模式与很多其它的模式都有着广泛的联系。Strategy很容易和Bridge模式相混淆。虽然它们结构很相似,但它们却是为解决不同的问题而设计的。Strategy模式注重于算法的封装,而Bridge模式注重于分离抽象和实现,为一个抽象体系提供不同的实现。Bridge模式与Strategy模式都很好的体现了"Favor composite over inheritance"的观点。

    ---------------------------------

    工厂模式:根据你给出的目的来生产不同用途的斧子,例如要砍人,那么工厂生产砍人斧子,要伐木就生产伐木斧子。
    即根据你给出一些属性来生产不同行为的一类对象返回给你。
    关注对象创建


    策略模式:用工厂生产的斧子来做对应的事情,例如用砍人的斧子来砍人,用伐木的斧子来伐木。
    即根据你给出对应的对象来执行对应的方法。
    关注行为的选择

  • 相关阅读:
    A Famous City
    A Famous ICPC Team
    配置单元测试环境,找不到SenTestingKit
    linux解压.tar命令
    语音输入——科大讯飞
    查看dsym错误信息
    工程里关闭arc
    导入签名错误
    mac显示隐藏文件
    类uialertview弹出动画
  • 原文地址:https://www.cnblogs.com/manmanlu/p/5741131.html
Copyright © 2020-2023  润新知