《大话》中关于策略模式是通过日常生活中常见的事物:商场打折促销引出的。每逢佳节,各大超市,购物中心就会涌现出五花八门的打折促销活动吸引顾客消费。自然,超市的购物结算系统就有了需求:根据不同的打折促销策略计费收银。这个代码怎么写呢?
通常情况下,初学者的思路很直接,他们认为这不是什么问题:直接就在确定按钮的click事件处理程序里,各种if-else ,switch统统搞起,就像下面这样:
///初学者代码
///
double total=0;
//这里只写了click事件的处理程序大概,load事件省略
private void btnOK_Click(object sender , EventArgs e)
{
double totalPrice=0d;
//根据下拉列表选择相应的打折方式
switch(cbxType.selectedIndex)
{
case 0:
totalPrice=Convert.todouble(txtPricr.text)*Convert.toDouble(txtNum.text);
break;
//打八折
case 1:
totalPrice=Convert.todouble(txtPricr.text)*Convert.toDouble(txtNum.text)*0.8;
.......//以下根据需求的变化,省略N段代码.....
}
}
如果商场的促销手段发生了变化,得,麻烦来了,要动大手术修改先前写的代码,于是乎,加班就不可避免的产生了!!!!
怎么办?策略模式可以很好的解决类似的问题(当然,她不是唯一的解决方法)。它的定义如下:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文: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(具体策略类): 2、 实现了Strategy定义的接口,提供具体的算法实现。商场的各种优惠活动,从本质上看,都是算法的不同,即计算过程不同,折扣有折扣的算法,返利有返利的算法,但是它们都有相同点:返回最后的收费金额(acceptCash方法)。根据面向对象的设计思想,抽取共同部分,形成抽象类。如下:
/// <summary>
/// 抽象算法类(现金收费抽象类)
/// </summary>
abstract class CashSuper
{
//定义算法接口
//收费金额方法
public abstract double acceptCash(double money);
}
具体的策略类:(这里为了简洁,只写其中一个)
/// <summary>
/// 正常收费子类
/// </summary>
class CashNormal:CashSuper
{
public override double acceptCash(double money)
{
return money;
}
}
刚才说了,三种不同的计算类,调用接口都是一样的,返回实收金额,则,这里添加一个CashContext类,用以维护cashsuper类的引用,根据不同的需求,进行不同的收费。
class CashContext
{
private CashSuper cs;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="csuper">初始化通过构造方法,传入具体的收费策略</param>
public CashContext(CashSuper csuper)
{
this.cs = csuper;
}
/// <summary>
/// 获得计算结果
/// 由于CashSuper是私有的,这里再次封装供外界调用
/// </summary>
/// <param name="money"></param>
/// <returns></returns>
public double GetResult(double money)
{
return cs.acceptCash(money);
}
}
客户端的代码如下:
/// <summary>
/// 客户端的代码
/// </summary>
public partial class Form1 : Form
{
double total = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void btnOK_Click(object sender, EventArgs e)
{
/*CashContext cc = null;
switch(cmbMethod.SelectedItem.ToString())
{
case "正常收费":
cc = new CashContext(new CashNormal());
break;
case "满300返100":
cc = new CashContext(new CashReturn("300","100"));
break;
case "打八折":
cc = new CashContext(new CashRebate("0.8"));
break;
}*/
///改版后的客户端代码
CashContext csuper = new CashContext(cmbMethod.SelectedItem.ToString());
double totalPrice = 0d;
totalPrice = csuper.GetResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtCount.Text));
total += totalPrice;
listBox1.Items.Add("单价:"+txtPrice.Text+"数量:"+txtCount.Text+""+cmbMethod.SelectedItem+"合计:"+totalPrice.ToString());
lblAll.Text = total.ToString();
}
}
仔细看看,觉得这个客户端的代码还是不够好,为什么呢?因为实例化具体的策略对象要由客户端来做,比较繁琐,而且可拓展性比较差,如果以后需求发生了改变,增加了新的策略类,那么客户端就要进行更改。(这就是变化点)
面向对象的宗旨就是封装变化点,如何才能封装客户端的变化?回忆起简单工厂模式,只用通过传入字符串就可以实力化出相应的对象,那么这里是否可以使用同样的方法,使得对于客户端而言,接口粒度再粗一点呢?答案是YES。结合简单工厂模式,更改如下:
///与简单工厂模式相结合
///CashContext改版
class CashContext
{
private CashSuper cc;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="csuper">初始化通过构造方法,传入字符串参数</param>
public CashContext(string type)
{
switch (type)
{
case "正常收费":
CashNormal cs0 = new CashNormal();
cc = cs0;
break;
case "满300返100":
CashReturn cs1 = new CashReturn("300","100");
cc = cs1;
break;
case "打八折":
CashRebate cs2 = new CashRebate("0.8");
cs = cs2;
break;
}
}
/// <summary>
/// 获得计算结果
/// 由于CashSuper是私有的,这里再次封装供外界调用
/// </summary>
/// <param name="money"></param>
/// <returns></returns>
public double GetResult2(double money)
{
return cs.acceptCash(money);
}
}
则,客户端对应更改为:
///改版后的客户端代码
CashContext csuper = new CashContext(cmbMethod.SelectedItem.ToString());
double totalPrice = 0d;
totalPrice = csuper.GetResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtCount.Text));
total += totalPrice;
listBox1.Items.Add("单价:"+txtPrice.Text+"数量:"+txtCount.Text+""+cmbMethod.SelectedItem+"合计:"+totalPrice.ToString());
lblAll.Text = total.ToString();
改造后的客户端代码简洁多了。
UML:
总结:今天学习的策略模式和上次学习的简单工厂模式看上去很相似,其实质都是封装变化点,策略模式用于封装不同的算法(规则),在分析过程中遇到需要在不同时间应用不同的业务规则时候,就可以使用策略模式来处理。