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

端午节到了,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原则和常用设计原则,高内聚、低偶合。 

 

策略模式的缺点

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

 

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

posted @ 2013-06-14 15:55  HongLiang861205  阅读(436)  评论(0编辑  收藏  举报