• 深入浅出设计模式系列(二):策略模式


    端午节到了,Jacky在家没事做,就跑去自己学长Cook的家串门。两个人聊得甚欢,Cook突然兴起,说,Jacky,今天我让你做个程序题,你想不想做。踌躇满志想成为一个优秀的程序员的Jacky当然毫不犹豫的就答应了。

    Jacky:来吧,什么题目啊?没有难度的题目可别出哦!

    Cook:你小子,长成了是吧?别看到时候出的题目简单,里面学问可不少呢。

    Jacky:好吧,那你就别废话了,快说什么题目?

    Cook:嗯,是这样的,就做一个商场的收营程序,程序要记录买的商品单价、商品数量,商品价格并且计算出商品的总价。

    Jacky:就这个啊?那还不容易啊?不就是在一个窗体上放两个文本框分别用来填写商品单价、商品数量,然后用一个列表框来记录商品的清单,一个标签来记录总计,一个确定按钮来算出每种商品的费用。对了,还要一个重置按钮,来重新开始。

    说着Jacky就去写代码了,不一会儿,半小时不到代码就写好了。他是这么写的

     1 private void btnOK_Click(object sender, EventArgs e)
     2 {
     3     double total = 0.0d;
     4 
     5     double totalPrice = Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text);
     6 
     7     total += totalPrice;
     8 
     9     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());
    10 
    11     lblTotal.Text += total.ToString();
    12 }

    Cook看了看Jacky写的代码,摇了摇头对Jacky说

    Cook:你看看,你这么写的话,如果我要现在又有一个新的需求,商店端午小长假促销活动,所有商品打8折,那你要怎么办啊?

    Jacky:那也很简单啊,只有修改一行代码就行了啊,只要把代码改成下面那样就Ok了,这样就是打八折了。

    1 double totalPrice = Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text) * 0.8;

    Cook:那如果端午节过了呢?不打折了,你这套程序岂不是会让商场蒙受巨大的损失啊?

    Jacky:那么到时候不打折了,我再把程序改回来就OK了啊。

    Cook:现在一年四季那么多节日,打折促销活动又层出不穷的。一会儿打折让利,一会儿满300返100的活动让利,又一会儿消费满200积分50的活动,轮番上阵,那你岂不是得累死啊?跟着他们不停的修改程序,部署客户端,这样你不觉得麻烦吗?你不觉得麻烦,人家商场的领导都觉得麻烦了。

    Jacky:啊。。。我好像是想的有些太简单了。那要不我增加一个下拉框,添加一些既定的打着方式,到时候让他们营业员来选择,这样就可以省掉一些麻烦事了。

    就这样,Jacky又跑回电脑旁去改写自己刚才的程序,添加了一个让利下拉框,供客户程序进行选择。他是这么写的

     1 private void btnOK_Click(object sender, EventArgs e)
     2 {
     3     double total = 0.0d;
     4     double totalPrice = 0.0d;
     5 
     6     switch (cmbType.SelectedText)
     7     {
     8         case "正常收费":
     9             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice);
    10             break;
    11         case "五折":
    12             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.5;
    13             break;
    14         case "六折":
    15             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.6;
    16             break;
    17         case "七折":
    18             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.7;
    19             break;
    20         case "八折":
    21             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) * 0.8;
    22             break;
    23         case "满300减100":
    24             totalPrice = Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) - Math.Floor(Convert.ToDouble(txtProductNumber) * Convert.ToDouble(txtProductUnitPrice) / 300) * 100;
    25             break;
    26         default:
    27             break;
    28     }
    29 
    30     total += totalPrice;
    31 
    32     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());
    33 
    34     lblTotal.Text += total.ToString();
    35 }

    Jacky兴高采烈的跑去让Cook看他改进后的代码

    Jacky:Cook,你快来,快来帮我看看,我把程序改进过了。

    Cook:嗯,我看看,哦,这样子写的确是比先前的代码要灵活些了。不过还是不够好,你看看你的代码,打5折和打6折,7折,8折有什么区别?就是最后的乘数不同,你干嘛要写那么多重复的代码?看看能不能进行封装一下啊?看来我之前教你的,都白教了,你都给忘记了。

    Jacky:哦哦,对哦,这个可以使用简单工厂方式,是吧?

    一溜烟,Jacky又跑去电脑边写起了代码,很快他就写好了代码,拿来给Cook看,他是这么写的

      1 //抽象产品类
      2 public abstract class CashSuper
      3 {
      4     public abstract double AcceptCash(double money);
      5 }
      6 
      7 //正常收费产品类
      8 public class CashNomal : CashSuper
      9 {
     10     public override double AcceptCash(double money)
     11     {
     12         return money;
     13     }
     14 }
     15 
     16 //打折产品类
     17 public class CashDiscount : CashSuper
     18 {
     19     public double Rate { get; set; }
     20 
     21     public CashDiscount(double rate)
     22     {
     23         this.Rate = rate;
     24     }
     25 
     26     public override double AcceptCash(double money)
     27     {
     28         return money * Rate;
     29     }
     30 }
     31 
     32 //返现产品类
     33 public class CashBack : CashSuper
     34 {
     35     public double CashBackCondition { get; set; }
     36     public double BackMoney { get; set; }
     37 
     38     public CashBack(double cashBackCondition, double backMoney)
     39     {
     40         this.CashBackCondition = cashBackCondition;
     41         this.BackMoney = backMoney;
     42     }
     43 
     44     public override double AcceptCash(double money)
     45     {
     46         return money - (money / CashBackCondition) * BackMoney;
     47     }
     48 }
     49 
     50 //工厂类
     51 public class PayMoneyFactory
     52 {
     53     public static CashSuper CreateAcceptCash(string payType)
     54     {
     55         CashSuper cashSuper = null;
     56 
     57         switch (payType)
     58         {
     59             case "正常收费":
     60                 cashSuper = new CashNomal();
     61                 break;
     62             case "五折":
     63                 cashSuper = new CashDiscount(0.5);
     64                 break;
     65             case "六折":
     66                 cashSuper = new CashDiscount(0.6);
     67                 break;
     68             case "七折":
     69                 cashSuper = new CashDiscount(0.7);
     70                 break;
     71             case "八折":
     72                 cashSuper = new CashDiscount(0.8);
     73                 break;
     74             case "满300减100":
     75                 cashSuper = new CashBack(300, 100);
     76                 break;
     77             case "满200减50":
     78                 cashSuper = new CashBack(200, 50);
     79                 break;
     80         }
     81 
     82         return cashSuper;
     83     }
     84 }
     85 
     86 //客户端代码
     87 private void btnOK_Click(object sender, EventArgs e)
     88 {
     89     double total = 0.0d;
     90     double totalPrice = 0.0d;
     91 
     92     CashSuper cashSuper = PayMoneyFactory.CreateAcceptCash(cmbType.SelectedText);
     93 
     94     totalPrice = cashSuper.AcceptCash(Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text));            
     95 
     96     total += totalPrice;
     97 
     98     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());
     99 
    100     lblTotal.Text += total.ToString();
    101 }

    Cook:嗯,我看看代码,Jacky写的不错啊,有长进,看来现在你对简单工厂模式掌握的不错啊。。。

    Jacky:那是当然的,你也不看看我是谁?

    Cook:好了,别又得意忘形了。简单工厂模式虽然也能解决这个问题,但这个模式只能解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场可能经常性的更改打折额度,每次维护和扩展收费方式都要改动这个工厂,以致于代码需要重新编译部署,这真是很糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,应该可以有更好的办法。今天我就教你一个新招。

    Jacky:又是什么新招?什么设计模式?你就别再卖关子了,快告诉我吧。

    Cook:嗯,那就是策略模式。来,看我来给你写代码。

      1 //抽象策略角色
      2 public abstract class CashSuper
      3 {
      4     public abstract double AcceptCash(double money);
      5 }
      6 
      7 //正常收费策略
      8 public class CashNomal : CashSuper
      9 {
     10     public override double AcceptCash(double money)
     11     {
     12         return money;
     13     }
     14 }
     15 
     16 //打折策略
     17 public class CashDiscount : CashSuper
     18 {
     19     public double Rate { get; set; }
     20 
     21     public CashDiscount(double rate)
     22     {
     23         this.Rate = rate;
     24     }
     25 
     26     public override double AcceptCash(double money)
     27     {
     28         return money * Rate;
     29     }
     30 }
     31 
     32 //返现策略
     33 public class CashBack : CashSuper
     34 {
     35     public double CashBackCondition { get; set; }
     36     public double BackMoney { get; set; }
     37 
     38     public CashBack(double cashBackCondition, double backMoney)
     39     {
     40         this.CashBackCondition = cashBackCondition;
     41         this.BackMoney = backMoney;
     42     }
     43 
     44     public override double AcceptCash(double money)
     45     {
     46         return money - (money / CashBackCondition) * BackMoney;
     47     }
     48 }
     49 
     50 //环境角色,上下文,持有一个策略类的引用,供最终给客户端调用
     51 public class CashContext
     52 {
     53     private CashSuper cashSuper = null;
     54 
     55     public CashContext(string payType)
     56     {
     57         switch (payType)
     58         {
     59             case "正常收费":
     60                 cashSuper = new CashNomal();
     61                 break;
     62             case "五折":
     63                 cashSuper = new CashDiscount(0.5);
     64                 break;
     65             case "六折":
     66                 cashSuper = new CashDiscount(0.6);
     67                 break;
     68             case "七折":
     69                 cashSuper = new CashDiscount(0.7);
     70                 break;
     71             case "八折":
     72                 cashSuper = new CashDiscount(0.8);
     73                 break;
     74             case "满300减100":
     75                 cashSuper = new CashBack(300, 100);
     76                 break;
     77             case "满200减5100":
     78                 cashSuper = new CashBack(200, 50);
     79                 break;
     80         }
     81     }
     82 
     83     public double GetPayResult(double money)
     84     {
     85         return cashSuper.AcceptCash(money);
     86     }
     87 }
     88 
     89 //客户端代码
     90 private void btnOK_Click(object sender, EventArgs e)
     91 {
     92     double total = 0.0d;
     93     double totalPrice = 0.0d;
     94 
     95     CashContext context = new CashContext(cmbType.SelectedText);
     96     totalPrice = context.GetPayResult(Convert.ToDouble(txtProductNumber.Text) * Convert.ToDouble(txtProductUnitPrice.Text));
     97 
     98     total += totalPrice;
     99 
    100     lvProducts.Items.Add("商品数量:" + txtProductNumber.Text + " 商品单价:" + txtProductUnitPrice.Text + " 商品总价:" + totalPrice.ToString());
    101 
    102     lblTotal.Text += total.ToString();
    103 }

    Cook:看像这样商场收营如何促销,是打折还是返利,其实都是一些算法,算法本身只是一个策略,最重要这些算法随时都可能互相替换的,这就是变化点,封装变化点就是我们面向对象的一种重要的思维方式。这里有一个抽象类,这个抽象类定义了一个获取收营金额的方法,然后不同的策略算法类来继承自这个抽象类,实现这个抽象类的抽象方法计算收营金额,在CashContext类中,维护一个收营的父类对象,根据收营方式的不同,动态的调用不同策略类计算收营的方法。

    策略模式(Strategy Pattern)

    策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)

    Context(应用场景):

    1. 需要使用ConcreteStrategy提供的算法。 
    2. 内部维护一个Strategy的实例。 
    3. 负责动态设置运行时Strategy具体的实现算法。 
    4. 负责跟Strategy之间的交互和数据传递。 

    Strategy(抽象策略类): 

    1. 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。 

    ConcreteStrategy(具体策略类): 

    1. 实现了Strategy定义的接口,提供具体的算法实现。

    策略模式的组成

    • 抽象策略角色: 策略类,通常由一个接口或者抽象类实现。 
    • 具体策略角色:包装了相关的算法和行为。 
    • 环境角色:持有一个策略类的引用,最终给客户端调用。

    策略模式的应用场景 

    1. 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。(例如FlyBehavior和QuackBehavior) 
    2. 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如FlyBehavior和QuackBehavior的具体实现可任意变化或扩充) 
    3. 对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。

     

    策略模式的优点

    • 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。 
    • 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。 
    • 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。 

     

    策略模式的缺点

    • 由于每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量

    文章声明本文部分内容参考自《大话设计模式》,这是一本学习设计模式非常好的书。

  • 相关阅读:
    nginx js、css多个请求合并为一个请求(concat模块)
    Web客户端语言HTML、XHTML和XML相关知识介绍
    正则小略
    你可能不知道的5个功能强大的 HTML5 API
    你须知道的30个CSS选择器 »
    css3 media媒体查询器用法总结
    深入java虚拟机学习 -- 类的加载机制
    ElasticSearch和solr的差别
    idea 使用debugger技巧
    vue学习问题总结(一)
  • 原文地址:https://www.cnblogs.com/HLiang/p/strategy_pattern.html
Copyright © 2020-2023  润新知