读《大话设计模式》——②商场促销[策略模式]

题目: 做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。
方案一: 用两个文本框来输入单价和数量,一个确定按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,还需要一个重置按钮来重新开始。
在这里插入图片描述
商场收银系统v1.0关键代码

double total = 0.0d;
private void btnOK_Click(object sender, EventArgs e)
{
	double totalPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text);
	total = total + totalPrice;
	lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " 合计:" + totalPrice.ToString());
	lblResult.Text = total.ToString();
}

存在问题

新增需求,商场搞活动,所有商品打八折,如何?若只在totalPrice乘以0.8,则活动结束难道还要再改一遍代码,重新给所有机器安装一次?显然是不合适的。那么考虑加个下拉选择框呢?

方案二
在这里插入图片描述
商场收银系统v1.1关键代码

double total = 0d;
private void Form2_Load(object sender, EventArgs e)
{
	cmbType.Items.AddRange(new object[] { "正常收费", "打八折", "打七折", "打五折" });
	cmbType.SelectedIndex = 0;
}

private void btnOK_Click(object sender, EventArgs e)
{
	double totalPrice = 0d;
	switch (cmbType.SelectedIndex)
	{
		case 0:
			totalPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text);
			break;
		case 1:
			totalPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) * 0.8;
			break;
		case 2:
			totalPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) * 0.7;
			break;
		case 3:
			totalPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text) * 0.5;
			break;
		default:
			break;
	}
	total = total + totalPrice;
	lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " " + cmbType.SelectedItem + " 合计:" + totalPrice.ToString());
	lblResult.Text = total.ToString();
}

存在问题:

  1. 灵活性好多了,但重复代码很多,如Convert.ToDouble()
  2. 4个分支除折扣不一样以外几乎没有不同,应该考虑重构
  3. 新增需求,活动加大,需要有满300返100的促销算法,怎么办?

方案三: 用前面学过的简单工厂模式,写一个父类和多个打折和返利的子类,利用多态实现。
注意不是写多个子类,只写两个子类即可,打折和返利只是传参不同。
面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

/// <summary>
/// 现金收费抽象类
/// </summary>
abstract class CashSuper
{
	/// <summary>
	/// 现金收取
	/// </summary>
	/// <param name="money">原价</param>
	/// <returns>当前价</returns>
	public abstract double acceptCash(double money);
}

/// <summary>
/// 正常收费子类
/// </summary>
class CashNormal : CashSuper
{
	public override double acceptCash(double money)
	{
		return money;
	}
}

/// <summary>
/// 打折收费子类
/// </summary>
class CashRebate : CashSuper
{
	private double moneyRebate = 1d;
	/// <summary>
	/// 实例化打折类
	/// </summary>
	/// <param name="moneyRebate">折扣率,如八折就是0.8</param>
	public CashRebate(double moneyRebate)
	{
		this.moneyRebate = moneyRebate;
	}

	public override double acceptCash(double money)
	{
		return money * moneyRebate;
	}
}

/// <summary>
/// 返利收费子类
/// </summary>
class CashReturn : CashSuper
{
	private double moneyCondition = 0d;
	private double moneyReturn = 0d;
	/// <summary>
	/// 实例化返利类
	/// </summary>
	/// <param name="moneyCondition">返利条件</param>
	/// <param name="moneyReturn">返利值</param>
	/// <example>比如满300返100,则moneyCondition=300,moneyReturn=100</example>
	public CashReturn(double moneyCondition, double moneyReturn)
	{
		this.moneyCondition = moneyCondition;
		this.moneyReturn = moneyReturn;
	}

	public override double acceptCash(double money)
	{
		double result = money;
		if (money >= moneyCondition)
			result = money - Math.Floor(money / moneyCondition) * moneyReturn;
		return result;
	}
}

/// <summary>
/// 现金收费工厂类
/// </summary>
class CashFactory
{
	public static CashSuper createCashAccept(string type)
	{
		CashSuper cs = null;
		switch (type)
		{
			case "正常收费":
				cs = new CashNormal();
				break;
			case "满300返100":
				cs = new CashReturn(300, 100);
				break;
			case "打八折":
				cs = new CashRebate(0.8);
				break;
			default:
				break;
		}
		return cs;
	}
}

客户端关键代码

double total = 0d;
private void Form3_Load(object sender, EventArgs e)
{
	cmbType.Items.AddRange(new object[] { "正常收费", "打八折", "满300返100" });
	cmbType.SelectedIndex = 0;
}

private void btnOK_Click(object sender, EventArgs e)
{
	CashSuper csuper = CashFactory.createCashAccept(cmbType.SelectedItem.ToString());
	double totalPrice = csuper.acceptCash(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
	total = total + totalPrice;
	lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " " + cmbType.SelectedItem + " 合计:" + totalPrice.ToString());
	lblResult.Text = total.ToString();
}

存在问题: 简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性的更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署。

方案四:策略模式(Strategy) 定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的用户。

策略模式结构图

在这里插入图片描述
因此,CashSuper、CashNormal、CashRebate、CashReturn不用改,添加策略类,修改客户端即可

/// <summary>
/// 现金收费策略类
/// </summary>
class CashContext
{
	private CashSuper cs;
	public CashContext(CashSuper cspuer)
	{
		this.cs = cspuer;
	}

	public double GetResult(double money)
	{
		return cs.acceptCash(money);
	}
}

客户端

private void Form4_Load(object sender, EventArgs e)
{
	cmbType.Items.AddRange(new object[] { "正常收费", "打八折", "满300返100" });
	cmbType.SelectedIndex = 0;
}

private void btnOK_Click(object sender, EventArgs e)
{
	CashContext cc = null;
	switch (cmbType.SelectedIndex)
	{
		case 0:
			cc = new CashContext(new CashNormal());
			break;
		case 1:
			cc = new CashContext(new CashRebate(0.8));
			break;
		case 2:
			cc = new CashContext(new CashReturn(300, 100));
			break;
		default:
			break;
	}
	double totalPrice = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
	total = total + totalPrice;
	lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " " + cmbType.SelectedItem + " 合计:" + totalPrice.ToString());
	lblResult.Text = total.ToString();
}

存在问题: 又回到方案二的情况,在客户端去判断使用哪个算法,怎么样才能把判断过程从客户端移走呢?

方案五:策略模式(Strategy) +工厂模式

/// <summary>
/// 改造后的现金收费策略类
/// </summary>
class CashContext1
{
	private CashSuper cs;
	public CashContext1(string type)
	{
		CashSuper cs1 = null;
		switch (type)
		{
			case "正常收费":
				cs = new CashNormal();
				break;
			case "满300返100":
				cs = new CashReturn(300, 100);
				break;
			case "打八折":
				cs = new CashRebate(0.8);
				break;
			default:
				break;
		}
		this.cs = cs1;
	}

	public double GetResult(double money)
	{
		return cs.acceptCash(money);
	}
}

客户端代码

double total = 0d;
private void Form4_Load(object sender, EventArgs e)
{
	cmbType.Items.AddRange(new object[] { "正常收费", "打八折", "满300返100" });
	cmbType.SelectedIndex = 0;
}

private void btnOK_Click(object sender, EventArgs e)
{
	CashContext1 cc = new CashContext1(cmbType.SelectedItem.ToString());
	double totalPrice = cc.GetResult(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
	total = total + totalPrice;
	lbxList.Items.Add("单价:" + txtPrice.Text + " 数量:" + txtNum.Text + " " + cmbType.SelectedItem + " 合计:" + totalPrice.ToString());
	lblResult.Text = total.ToString();
}

总结: 对比一下简单工厂模式和策略模式,会发现,简单工厂模式需要客户端认识CashSuper和CashFactory两个类,而策略模式只需要客户端认识CashContext即可。

策略模式解析:

  • 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类质检的耦合。
  • 优点1:策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
  • 优点2:简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
  • 优点3:策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不用的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
  • 缺点:CashContext中还是用到了switch,也就是说,如果需要增加算法,就必须要更改CashContext中的switch代码【解决此缺点的方式就是反射技术,会在抽象工厂模式中讲解】

综上所述,除了策略模式比简单工厂模式耦合略低(需要认识的类少)外,其他优点笔者还体会不到,有待深入理解,欢迎各位大神指点。
QQ:601249408

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值