商场收银软件
一个商场收银软件,营业员根据客户所购买的商品的单价和数量,向客户收费。用两个文本框来输入单价和数量,一个确定按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,一个重置按钮来重新开始。
第一版代码
double total = 0.0d;
private void btn0k_Click(object sender, EventArgs e)
{
double totalPrices = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text);
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
增加打折功能
比如遇到节假日需要增加打折功能:
double total = 0.0d;
private void Form_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[] {"正常收费", "打八折", "打五折"});
cbxType.SelectedIndex = 0;
}
private void btn0k_Click(object sender, EventArgs e)
{
double totalPrices = 0.0d;
switch(cbxType.SelectedIndex)
{
case 0:
totalPrices = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text);
break;
case 1:
totalPrices = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) * 0.8;
break;
case 2:
totalPrices = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) * 0.5;
break;
}
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
简单工厂实现
面对对象的编程,并不是类越多越好,类的划分是为了封装,但是封装的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。打一折和打九折只是形式的不同,抽象分析出来,所有的打折算法都是一样的,所以打折算法应该是一个类。
现金收费抽象类
abstract class CashSuper
{
public abstract double AcceptCash(double money);
}
正常收费子类
class CashNormal : CashSuper
{
public override double AcceptCash(double money)
{
return money;
}
}
打折收费子类
class CashRebate : CashSuper
{
private double moneyRebate = 1d;
public CashRebate(string moneyRebate)
{
this.moneyRebate = double.Parse(moneyRebate);
}
public override double AcceptCash(double money)
{
return money * moneyRebate;
}
}
返利收费子类
class CashReturn : CashSuper
{
private double moneyCondition = 0.0d;
private double moneyReturn = 0.0d;
public CashReturn(string moneyCondition, string moneyReturn)
{
this.moneyCondition = double.Parse(moneyCondition);
this.moneyReturn = double.Parse(moneyReturn);
}
public override double AcceptCash(double money)
{
double result = money;
if (money >= moneyCondition)
{
result = money - Math.Floor(money / moneyCondition) * moneyReturn;
}
return result;
}
}
现金收费工厂类
class CashFactory
{
public static CashSuper CreateCashAccept(string type)
{
CashSuper cs = null;
switch (type)
{
case "正常收费":
cs = new CashNormal();
break;
case "满300返100":
CashReturn cr1 = new CashReturn("300", "100");
cs = cr1;
break;
case "打8折":
CashRebate cr2 = new CashRebate("0.8");
cs = cr2;
break;
}
return cs;
}
}
客户端程序
double total = 0.0d;
private void btn0k_Click(object sender, EventArgs e)
{
CashSuper csuper = CashFactory.CreateCashAccept(cbxType.SelectedItem.ToString());
double totalPrices = 0d;
totalPrices = csuper.AcceptCash(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
简单工厂模式虽然能解决这个问题,但是这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式。
策略模式
策略模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
CashContext类
class CashContext
{
private CashSuper cs;
public CashContext(CashSuper csuper)
{
this.cs = csuper;
}
public double GetResult(double money)
{
return cs.AcceptCash(money);
}
}
客户端主要代码
double total = 0.0d;
private void btn0k_Click(object sender, EventArgs e)
{
CashContext cc = null;
switch (cbxType.SelectedItem.ToString())
{
case "正常收费":
cc = new CashContext(new CashNormal());
break;
case "满300返100":
cc = new CashContext(new CashReturn("300", "100"));
break;
case "打8折":
cc = new CashContext(new CashRebate("0.8"));
break;
}
double totalPrices = 0d;
totalPrices = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
虽然策略模式写出来了,但是不应该让客户端去判断用哪一个算法。
策略与简单工厂结合
class CashContext
{
CashSuper cs = null;
public CashContext(string type)
{
switch (type)
{
case "正常收费":
CashNormal cs0 = new CashNormal();
cs = cs0;
break;
case "满300返100":
CashReturn cr1 = new CashReturn("300", "100");
cs = cr1;
break;
case "打8折":
CashRebate cr2 = new CashRebate("0.8");
cs = cr2;
break;
}
}
public double GetResult(double money)
{
return cs.AcceptCash(money);
}
}
客户端代码
double total = 0.0d;
private void btn0k_Click(object sender, EventArgs e)
{
CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
double totalPrices = 0d;
totalPrices = csuper.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
对比
// 简单工厂模式的用法
CashSuper csuper = CashFactory.CreateCashAccept(cbxType.SelectedItem.ToString());
double result = csuper.GetResult();
// 策略模式与简单工厂结合的用法
CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
double result = csuper.GetResult();
简单工厂模式让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂结合的用法,客户端只需要认识一个类CashContext就可以了。耦合更加降低。
策略模式解析
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少各种算法类和使用算法类之间的耦合。
策略模式就是用来封装算法的,但是在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同的时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
模式优点
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为,如果不使用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样算法或行为的逻辑就和算法或行为的环境类混合在一起了。
模式缺点
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
策略模式造成很多的策略类,每个具体策略类都会产生一个新类。
适用场景
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。