策略模式

 策略模式

  作者:崔涛涛(07770225)

  今天我要在这里介绍的是C#设计模式中的策略模式。

  介绍策略模式之前,我们先在这里看个我虚构与今天我要讲解的模式有关的简短的问题。

问题:如今网购盛行,很多人都应该在网上买过东西,虚拟物品或者是非虚拟物品。在B2C的网络购物网站中,商家为了吸引买家常会推出比较优惠的折扣。现在有家B2C的购物网站,它的折扣方式是这样的,对于VIP来说基本的折扣率是20%,但当一些重要的节假日来到时,为了与别的B2C网站竞争还会为VIP提供一个额外的折扣率5%。当然该网站的也有促销的时候,网站会为VIP提供20%的促销产品折扣。这三个折扣方案不会相互覆盖。现在让我们来试着为这个网站的实现折扣系统。

 

  倘若您作为这个折扣模块的编写者,一般情况下您会如何来处理这个问题。

  不假思索的方案

  我在这里展示我的不假思索的解决方案:

  根据上面这段文字的描述,我第一感觉是将折扣的实现过程与VIP封装到同一个类中。

  图如下:

注:VIP类中方法Discount()是来生成VIP的基本折扣,方法FestivalDiscount()是来生成节假日时提供的额外折扣,方法SaleDiscount()是来生成促销时提供的额外折扣,而GetDiscount()则是获取最终的折扣值。 

实现代码:

代码
1 /// <summary>
2 /// VIP 的基本折扣率
3 /// </summary>
4 /// <returns></returns>
5   private double Discount()
6 {
7 return 20.0 / 100.0;
8 }
9
10 /// <summary>
11 /// 计算节假日中的额外折扣
12 /// </summary>
13 /// <returns></returns>
14 private double FestivalDiscount()
15 {
16 return 5.0 / 100.0;
17 }
18
19 /// <summary>
20 /// 计算促销时期的额外折扣
21 /// </summary>
22 /// <returns></returns>
23 private double SaleDiscount()
24 {
25 return 20.0 / 100.0;
26 }
27
28 /// <summary>
29 /// 计算中的折扣
30 /// </summary>
31 /// <param name="pType">
32 /// 1表示 既不是节假日,也没有促销活动;
33 /// 2表示 节假日,但是没有促销;
34 /// 3表示 促销,但不是节假日;
35 /// 4表示 即使节假日,还有促销活动。
36 /// </param>
37 /// <returns></returns>
38 public double GetDiscount(int pType)
39 {
40 switch (pType)
41 {
42 // 节假日,但是没有促销
43 case 2: return 1.0 - ( Discount() + FestivalDiscount()); break;
44 // 促销,但不是节假日
45 case 3: return 1.0 - (Discount() + SaleDiscount()); break;
46 // 即使节假日,还有促销活动
47 case 4: return 1.0 - ( Discount() + FestivalDiscount() + SaleDiscount()); break;
48 // 既不是节假日,也没有促销活动
49 default: return 1.0 - Discount();
50 }
51 }

 

 

 

 

         现在就让我们开探讨下这种方案中的缺陷。

         缺陷1:如果VIP类的实例有很多个,那么也会有相同个数的方法Dsicount(),FestivalDiscount(),SaleDiscount()。而且他们的执行过程一样。这样就出现了代码冗余。

         缺陷  2:在GetDisocunt()中,我用一个switch语句来实现不同的折扣方案间的选择。这样产生一个多重跳将转移结构。相信很多有过编程和调试程序经验的程序员都在多重条件转移语句下痛苦过。多重条件转移语句将逻辑与行为或算法混合在了一起,这将使代码变得不易维护。

         缺陷  3: 如果系统完成后,突然客户说现行的折扣方案未能吸引更多的顾客来购买商品,并提供了新的折扣方案来替换掉原先的方案, 新方案的逻辑过程与现行的完全不同。这替换将无法实现。

 

         下面是我用策略模式来解决不假思索带来的缺陷。

在这展示策略模式之前,我们先了解策略模式的目的或者是其所针对的问题。策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换。

   重点是将使用算法的责任和算法本身分割开,使得算法可以互换。

   目的是使算法可以在不影响到客户端的情况下发生变化。

    最终目的是尽量减少代码的维护量和可扩展性。

 

  策略模式的结构图:

运用策略模式后的类图:

 

注:VIP中的字段m_Discount是对接口IDiscount的引用

好的,现在让我们来看看策略模式是否解决了之前不假思索的缺陷。

1.  代码冗余缺陷:由于VIP是引用接口IDiscount,所以N多个VIP实例只需一个折扣方案类(DiscountOne或者DiscountTwo或者DiscountThree)的实例就可以了,无需一个VIP实例就有一个折扣方案实例。这样就减少了代码的冗余。

2.  多重条件转移语句:使用策略模式后,只需将相应的折扣方案类的实例传给VIP类的实例,不用每次都是用多重条件转移语句来进行选择。如VIP vip = new VIP(new DiscountThree());VIP vip = new VIP(new DiscountTWO());VIP vip = new VIP(new DiscountOne());因为VIP只调用接口的GetDiscount()方法。

3.  添加不同结构的新的折扣方案:由于VIP只是调用接口的GetDiscount()方法,不关心折扣方案的具体逻辑过程,所以当添加新的不同结构的折扣方案时,不会改变VIP的任何代码,新折扣方案就可以不差别的替换掉之前的折扣方案。

相关代码:

 

代码
1 按钮事件来作为测试模式的主程序
2 private void btnPattern1_Click(object sender, EventArgs e)
3 {
4 rtbMsg.Text = "";
5 rtbMsg.Text += "策略模式 \n";
6 VIP vip = new VIP(new DiscountThree());
7 rtbMsg.Text += "折扣是:" + (vip.Discount * 10).ToString() + "折。\n";
8
9 rtbMsg.Text += "\n\n";
10 rtbMsg.Text += "一般方法 \n";
11 _VIPDiscount vip4stars = new _VIPDiscount();
12 rtbMsg.Text += "折扣是:" + (vip4stars.GetDiscount(3) * 10).ToString() + "折。";
13 }
14 DiscountOne:
15 public class DiscountOne : IDiscount
16 {
17 #region IDiscount 成员
18
19 public double GetDiscount()
20 {
21 return Discount();
22 }
23
24 #endregion
25
26 private double Discount()
27 {
28 return 80 / 100.0;
29 }
30 }
31 DiscountTwo:
32 public class DiscountTwo : IDiscount
33 {
34 #region IDiscount 成员
35
36 public double GetDiscount()
37 {
38 return Discount() - FestivalDiscount();
39 }
40
41 #endregion
42
43 private double Discount()
44 {
45 return 80 / 100.0;
46 }
47
48 private double FestivalDiscount()
49 {
50 return 5.0 / 100.0;
51 }
52 }
53 DiscountThree:
54 public class DiscountThree : IDiscount
55 {
56 #region IDiscount 成员
57
58 public double GetDiscount()
59 {
60 return Discount() - FestivalDiscount() - SaleDiscount();
61 }
62
63 #endregion
64
65 private double Discount()
66 {
67 return 80 / 100.0;
68 }
69
70 private double FestivalDiscount()
71 {
72 return 5.0 / 100.0;
73 }
74
75 private double SaleDiscount()
76 {
77 return 20.0 / 100.0;
78 }
79 }
80 VIP类的Discount属性:
81 public Double Discount
82 {
83 get
84 {
85 return m_Discount.GetDiscount();
86 }
87 }
88

策略模式的缺点:

1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。

2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。


 其它

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

推荐大家读一读《IoC 容器和Dependency Injection 模式》,作者Martin Fowler。网上可以找到中文版的PDF文件。为策略模式的实施提供了一个非常好的方案。

参考资料:C#设计模式。

posted @ 2010-10-31 16:32  天津城建学院软件工程  阅读(636)  评论(0编辑  收藏  举报