《大话设计模式》学习笔记(Java实现)——第2章 策略模式

版权声明:本文为博主ExcelMann的原创文章,未经博主允许不得转载。

第2章 策略模式

作者:ExcelMann,转载需注明。

正文

本章介绍的是【策略模式】,要实现的需求是:做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。

关键知识

  1. 策略模式的介绍
  2. 策略模式和简单工厂模式的区别
  3. 策略模式和简单工厂模式的结合

备注:文章中的有关于界面的代码使用C#语言实现。

1.V1.0版本

需求:用两个文本框来输入单价和数量,一个确定按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总价,还有一个下拉选择框来选择打折的方式。

代码如下:

double total = 0.0d;

private void Form1_Load(object sender,EventArgs e)
{
	cbxType.Items.AddRange(new Object[] {"正常收费","打8折","打7折","打5折"});
	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.Items.Add("....");
	...
}

该代码出现的问题是:

  1. 重复代码很多!比如,Convert.ToDouble代码就出现了8次,如果再加一种新的情况,就还要再重复写2次,造成不可复用的问题;
  2. 各个case分支中的代码,除了打折,其他代码没有区别;
  3. 面对新的需求,又得是要拿到这个类,然后更新其中的代码,这造成维护性低的问题;
  4. 个人理解:业务逻辑代码和客户端(界面)代码耦合性太高。造成不可复用、维护性低、拓展性低、不够灵活的问题;

解决方法:利用简单工厂模式(封装+继承+多态)。

2.开始使用简单工厂模式

初始想法
先写一个父类,再继承它实现多个打折和返利的子类,利用多态,完成这个代码。

问题所在
需要写多少个子类?对于每个打折和返利都需要写一个子类吗?

关键
在封装的过程中,若要分类,要找出其中的不同和相同点。封装的关键是抽象,即找出具有相似属性和功能的对象集合作为类
在这里来分析的话,就是对于每个打折的类,抽象出来为"打(?)折类",对于每个返利的类,抽象出来为"达到(?)元返利(?)元的类"。

实现
在这里插入图片描述

public abstract class CaseSuper
{
	public abstract double acceptCash(double money);
}

public class CaseNormal extends CashSuper
{
	public double acceptCash(double money)
	{
		return money;
	}
}

public class CashRebate extends CashSuper
{
	private double moneyRebate = 1d;
	public CashRebate(String moneyRebate)
	{
		this.moneyRebate = moneyRebate;
	}
	public double acceptCash(double money)
	{
		return money * moneyRebate;
	}
}
// ...其他子类

// 工厂类
public class CashFactory
{
	public static CashSuper createCashAccept(String type)
	{
		CashSuper cs = null;
		switch(type){
			case "正常收费":
				cs = new CashNormal();
				break;
			// ...其他case
		}
		return cs;
	}
}

现在,已经实现了可维护(修改其中一个CashSuper子类代码,不影响其他类)、可拓展(可以通过增加新的子类继承CashSuper,来增加新的计算方式)、可复用(这些子类都可以用于其他的情形当中,而不用写重复代码)、灵活性高

新的问题
简单工厂模式虽然可以解决这个问题,但是工厂模式的目的还是用于解决对象的创建问题。如果每次要增加新的收费方式,或者维护旧的收费方式,都得要更新工厂方法,以致代码需要重新编译部署。

所以面对算法的时常变动,应该有更好的方法:策略模式

3.策略模式的介绍

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

结构图和代码
在这里插入图片描述

// 抽象算法类
public abstract class Strategy
{
	// 算法的共同方法
	public abstract void AlgorithmInterface();
}
// 具体算法A
public class ConcreteStrategyA extends Strategy
{
	// 实现算法的方法
	public void AlgorithmInterface()
	{
		...
	}
}
// ...其他具体实现算法

// Context上下文
public class Context
{
	private Strategy strategy;
	public Context(Strategy strategy) // 构造对象时,就指定实现的具体算法策略
	{
		this.strategy = strategy;
	}
	
	public void ContextInterface()
	{	
		strategy.AlgorithmInterface(); // 调用指定策略算法的方法
	}
}

关键点
我认为策略模式的关键点在于,抽象策略类抽象了这类策略的方法,而各个子策略通过不同的代码来实现该方法。对于Context上下文,负责切换各个算法,但是Context中的ContextInterface方法,调用的方法永远是抽象策略类所抽象的那个方法

书中原话
对于打折还是返利,其实这些都是算法,用工厂方法来生成算法对象本身没有错,但是算法本身是一种策略。最重要的是这些算法是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。

对于这句话我的理解是:要弄清楚工厂模式和策略模式之间的区别

  1. 从刻板的文字理解上分析:工厂模式主要解决的是对象创建的问题,但不是所有对象都是算法/策略;策略模式主要解决的是相似算法切换的问题,对于上述的那些算法(打折、返利),由父类来抽象出共同的方法,然后由Context上下文来切换不同的算法;
  2. 从实际应用上分析:如果使用工厂模式来达到策略模式的效果,那么你在客户端中,对于每个工厂模式获得的算法对象,调用算法方法的代码中,得确保调用的方法都是同一个;而对于策略模式,其在Context上下文中,通过一个ContextInterface方法就控制好了各个算法将要调用的方法,保证各个算法调用的方法都是为了同一个需求;
  3. 或者换个说法:工厂模式只是一个生产对象的工厂类,具体对对象做什么操作由客户端代码来决定;而策略模式在Context中管理对象,对象的操作都是由Context统一管理;

4.开始使用策略模式

商场收银系统新版本,代码结构图:
在这里插入图片描述
代码:

// CashContext类
public class CashContext
{
	private CashSuper cs;
	
	public CashContext(CashSuper cs) // 通过构造方法,设定具体算法策略
	{
		this.cs = cs;
	}

	public double getResult(double money){
		return cs.acceptCash(money); // 调用指定算法策略的该方法,计算Cash
	}
}

客户端主要代码(C#实现)

double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
	CashContext cc = null;
	switch(cbxType.SelectedItem.ToString())
	{
		case "正常收费":
			cc = new CashContext(new CashNormal()); // 通过构造方法的参数,指定具体策略算法
			break;
		// ...其他case
	}
	
	double totalPrices = 0d;
	// 调用cc.getResult()方法,可以得到收取费用的结果,让具体算法的实现与客户进行了隔离
	totalPrices = cc.getResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text));
	total = total + totalPrices;
	// ...其余界面更新数据代码
}

新的问题
switch判断的过程又是在客户端代码中出现。如何将客户端代码中的switch判断过程转移到其他地方呢?
答案是:采用简单工厂模式。
不过,这里的简单工厂模式不是一个单独的类来管理,而是借助策略模式的Context来管理。(简单工厂模式的另一种做法

5.将策略和简单工厂模式结合

改造后的CashContext:

public class CashContext
{
	CashSuper cs = null;
	// 将switch过程转移到CashContext的构造方法中。简单工厂的应用
	public CashContext(String type)
	{
		switch(type){
			case "正常收费":
				cs = new CashNormal();
				break;
			// ...其他case
		}
	}

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

客户端代码:

double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
	// 将之前的switch过程,化简为一个语句
	CashContext cc = new CashContext(cbxType.SelectedItem.ToString());
	
	double totalPrices = 0d;
	
	// 调用cc.getResult()方法,可以得到收取费用的结果,让具体算法的实现与客户进行了隔离
	totalPrices = cc.getResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text));
	total = total + totalPrices;
	// ...其余界面更新数据代码
}

6.策略模式的总结解析

原文句子

  1. 首先,反思一下策略模式,策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合
    我的理解:跟我在上面提到的策略模式和简单工厂模式的区别中提到的类似。
  2. 策略模式的另一个优点是:简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试
  3. 策略模式封装了变化:当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句
  4. 策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性
  5. 不足之处:在Context中还是使用了switch语句,不过其实可以使用反射机制来解决。在后续的抽象工厂模式中会提到。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值