需要做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。
初步想法是这样的:
用两个文本框来输入单价和数量,一个确定按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,用一个重置按钮来重新开始。
商场收银系统v1.1关键代码如下:
//声明一个double变量来计算总计
double total = 0.0d;
private void btnOk_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 Form1_Load(object sender, EventArgs e)
{
cbxType.Items.AddRange(new object[]{"正常收费", "打八折", "打七折", "打五折"});
cbxType.SelectedIndex = 0;
}
private void btnOk_Click(object sender, EventArgs e)
{
double totalPrices = 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.7;
break;
case 3:
totalPrices = Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text) * 0.5;
break;
}
total = total + totalPrices;
lbxList.Item.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + " " + cbxType.SelectedItem+"合计:" + totalPrices.ToString());
lblResult.Text = total.toString();
}
重复代码还是很多,比如Convert.ToDouble()这里写了8遍,而且四个分支要执行的语句除了打折多以外几乎没有什么不同,应该考虑重构一下。不过现在需求又变了,商场活动加大,需要有满300返100的促销算法。现在要用到简单工厂模式了。
特别注意:面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
现金收费抽象类
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 = 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)
{
//返利收费,初始化时必须要输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100
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 "打八折":
CashRebate cr2 = new CashRebate("0.8");
cs = cr2;
break;
}
return cs;
}
}
客户端主要部分:
double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
CashSuper csuper = CashFactory.createCashAccept(cbx.SelectedItem.ToString());
double totalPrices = 0d;
totalPrices = csuper.acceptCash(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:" + txtPrices.Text +"数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
如果商场现在需要增加一种促销手段,满100几分10点,以后积分到一定程度可以领取奖品,怎么做?
简单工厂虽然也能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包含了所有的收费方式,商场是可能经常更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这不是最好的处理方法。
策略模式(Strategy):
它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
Strategy类,支持所有支持的算法的公共接口
//抽象算法类
abstract class Strategy
{
//算法方法
public abstract void AlgorithmInterface();
}
ConcreateStrategy,封装了具体的算法或行为,继承于Strategy
//具体算法A
class ConcreteStrategyA : Strategy
{
//算法A实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法A实现");
}
}
//具体算法B
class ConcreteStrategyB : Strategy
{
//算法B实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法B实现");
}
}
//具体算法C
class ConcreteStrategyC : Strategy
{
//算法C实现方法
public override void AlgorithmInterface()
{
Console.WriteLine("算法C实现");
}
}
Context,用一个ConcreteStrategy来配置,维护对Strategy对象的引用。
//上下文
class Context
{
Strategy strategy;
public Context(Strategy strategy)
{
this.strategy = strategy;
}
//上下文接口
public void ContextInterface()
{
strategy.AlgorithmInterface();
}
}
客户端代码:
static void Main()
{
Context context;
context = new Context(new ConcreteStrategyA());
context.ContextInterface();
context = new Context(new ConcreteStrategyB());
context.ContextInterface();
context = new Context(new ConcreteStrategyB());
context.ContextInterface();
Console.Read();
}
现在我们利用策略模式来改一下我们之前的代码:
CashContext类:
class CashContext
{
private CashSuper cs;
public CashContext(CashSuper csuper)
{
this.cs = csuper;
}
public double GetResult()
{
return cs.acceptCash(money);
}
}
客户端代码:
double total = 0.0d;
private void btn_Ok(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 + "" + cbxType.SelectedItem + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
这样好像又是在客户端去判断使用哪一个算法了。怎么从客户端移走呢?
策略与简单工厂结合:
改造后的CashContext:
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;
}
}
}
客户端代码:
double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
double totalPrices = 0d;
totalPrices = cc.GetResult(Convert.ToDouble(txtPrice .Text) * Convert.ToDouble(txtNum.Text));
total = total + totalPrices;
lbxList.Items.Add("单价:"+ txtPrice.Text + "数量:" txtNum.Text + "" + cbxType.SelectedItem + "合计:" + totalPrices.ToString());
lblResult.Text = total.ToString();
}
解析:策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法都是完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能,另外一个优点就是简化了单元测试,因为每个算法都有 自己的类,可以通过自己的接口单独测试。
当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。
策略模式是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。