(2)设计模式之 策略模式 及 策略模式与简单工厂模式的结合

策略模式

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


我们继续以例子来阐述该模式
假如商场搞促销,有多种促销方式,促销方式包括:满300送80,打8折,满500送150等等,现在要求写出一个商场收银软件的的逻辑代码

其实这个要求用前面说过的简单工厂模式也一样可以实现,直白的说,简单工厂模式和策略模式的差别很小,而策略模式和简单工厂模式结合起来简单工厂模式自身比较的话,差距一般人看不出来…

如果用简单工厂模式来写,很简单,跟写计算器差不多(就是照着葫芦画瓢的过程),就不写代码了 其实就是懒

先上策略模式的UML图
请添加图片描述
CashContext叫做上下文类,为什么叫上下文类呢?因为是它连接了上下文!没错,其中上文就表示策略类(例子中的Cashsuper类),而下文就是各个具体的策略类,上下文类会根据打折要求的不同,而选择不同的具体的策略类,注意!:具体的策略类并不在上下文类中实例化,策略模式中的上下文类只负责选择
对照定义,一个策略类 ==>> 一个算法。

策略模式实现超市收银逻辑代码(JAVA):

package MarketPromotion;
//抽象的策略类(算法类)
public abstract class CashSuper {
	public abstract double acceptCash(double money);
}
package MarketPromotion;
//具体的策略类(算法类)
public class CashNormal extends CashSuper {

	@Override
	public double acceptCash(double money) {
		// TODO Auto-generated method stub
		return money;
	}

}
package MarketPromotion;
//具体的策略类(算法类)
public class CashRebate extends CashSuper {
	private double moneyRebate=0;
	public CashRebate(String moneyRebate) {
		this.moneyRebate=Double.parseDouble(moneyRebate);
	}
	@Override
	public double acceptCash(double money) {
		// TODO Auto-generated method stub
		return money*moneyRebate;
	}

}
package MarketPromotion;
//具体的策略类(算法类)
public class CashReturn extends CashSuper {
	private double moneyCondition=0;
	private double moneyReturn=0;
	
	public CashReturn(String moneyCondition, String moneyReturn) {
		this.moneyCondition = Double.parseDouble(moneyCondition);
		this.moneyReturn = Double.parseDouble(moneyReturn);
	}
	@Override
	public double acceptCash(double money) {
		double result = money;
		if(money >= moneyCondition){
			result = money - Math.floor(money/moneyCondition) * moneyReturn;
		}
		return result;
	}

}
package MarketPromotion;
//上下文类
public class CashContext {
	private CashSuper cs=null;
	
	public  CashContext(CashSuper csuper){//只对策略做了选择,并没有实例化
		this.cs=csuper;
	}
	public double GetResult(double money){
		return cs.acceptCash(money);
	}
}
package MarketPromotion;

public class CashTest {

	public static void main(String[] args) {
		CashContext cc=null;
		String[] strs={"正常收费","满300送100","打8折"};
		switch (strs[1]) {//实例化放在了客户端
		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;
		default:
			break;
		}
		double result = 0;
		double money = 400;
		result=cc.GetResult(money);
		System.out.println("原价:"+money+"\n折后总计:"+result);
	}

}

发现问题了没有?
这客户端的代码也太复杂了吧,怎么能把这么多底层逻辑性的代码放到客户端呢?策略模式虽然解决了简单工厂模式的缺点,但是却有点拆了东墙补西墙的意思…

但是我们是万万不能让底层逻辑性代码出现在客户端的,因为客户端是客户可以看到的啊,被客户看到,我的技术就被客户偷学走了,我还赚什么钱? ,只要是可以被看到,就容易被攻击,其安全性就降低了。

于是就有了策略模式和简单工厂模式的结合。
先上UML图
请添加图片描述

我k?这跟策略模式有什么不同啊?UML图长得这么像。
漏,大漏特漏!
看好上下文类到抽象策略类的箭头,一个是实心的一个是空心的好不好,这在代码中体现出来的差别大着呢

那现在就需要解释一下实心箭头和空心箭头的区别,合成聚合关系,如果不是没学过设计模式,肯定听说过这个名词,但是合成聚合关系却是两个不同的关系。

空心的表示聚合关系,聚合表示一种弱的“拥有”关系,体现的是A对象可以包含B对象,但B对象不是A对昂的一部分。
实心的表示合成关系,合成是一种强的“拥有”关系,体现了严格的整体和部分的关系,部分和整体的生命周期一样。

如果看不懂,也没关系,我们这里只知道这两个的一个重要区别就可以了,就是部分和整体的生命周期,策略模式刚才的代码也体现出了,来看上下文类的代码:

public class CashContext {
	private CashSuper cs=null;
	
	public  CashContext(CashSuper csuper){//只对策略做了选择,并没有实例化
		this.cs=csuper;
	}
	public double GetResult(double money){
		return cs.acceptCash(money);
	}
}

当你实例化出上下文类的时候,策略类其实是没有被实例化的,只是对策略进行了简单的选择,这时这两者的关系叫做聚合。
而如果让上下文和策略的关系变为合成关系,在实例化上下文类的时候就要同时把策略类实例化出来,以保证两者生命周期相同,代码:

public class CashContext {
	private CashSuper cs=null;
	
	public CashContext(String type){
		switch (type) {
		case "正常收费":
			CashNormal cs0=new CashNormal();
			cs=cs0;
			break;
		case "满300送100":
			CashReturn cs1=new CashReturn("300","100");
			cs=cs1;
			break;
		case "打8折":
			CashRebate cs2=new CashRebate("0.8");
			cs=cs2;
			break;
		default:
			break;
		}
	}
	public double GetResult(double money){
		return cs.acceptCash(money);
	}
}

这样做就可以把底层逻辑代码挪到了客户端,没错,如果你也学习了简单工厂模式,你就会说,简单工厂也可以做到啊,对,简单工厂也可以做到,而且这两者的代码区别非常小,对比一下就知道了

//简单工厂类
public class OperationFactory {
	public static Operation createOperation(String operate){
		Operation oper = null;
		switch (operate) {
		case "+":
			oper=new OperationAdd();
			break;
		case "-":
			oper=new OperationSub();
			break;
		case "/":
			oper=new OperationDiv();
			break;
		case "*":
			oper=new OperationMul();
			break;
		default:
			break;
		}
		return oper;
		
	}
}

没错,做逻辑判断的代码,一个是构造函数,一个则是重新定义了一个函数,其实这样做还有一个好处,就是在客户端的代码可以让客户少知道一个函数,如果是简单工厂模式,客户端会多出来调用createOperation()的方法,如果是两者的结合,客户端就不会出现这一函数,客户所知道的信息更少了,程序也变得更安全了。

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的方法,减少了各种算法类与使用算法类之间的耦合。
策略模式Strategy类层次为Context定义了一系列可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
策略模式的优点是简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为,将这些行为封装在一个个独立的策略类中,可以在使用这些行为的类中消除条件语句。
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
但是在基本的策略模式中,选择所用的具体实现的职责由客户端对象承担,并转给则略模式的上下文类。所以就有了策略模式和简单工厂的结合。


策略模式的反向调用

一个公司在对员工进行工资的发放的时候,有很多方式,其中可能包括人民币现金支付、美元现金支付,银行卡支付等。
利用策略模式实现该功能。

首先我们先对问题进行分析,显然银行卡支付比现金支付多了一个属性——银行卡号,那么我们应该如何解决该问题呢?

显然容易想到的是新建一个具体策略类,并且添加一个卡号的属性。这样固然可以实现该功能。

其实还有一个办法,就是通过扩展上下文对象来实现卡号属性的添加。
我们下面的代码和UML图就是通过扩展上下文类来实现的该功能,会在末尾对这两种方法进行对比分析。

UML图
在这里插入图片描述
代码:

package Salary;

public interface SalaryPayStrategy {
	public void pay(PaymentContext ctx);
}
package Salary;

public class RMBCash implements SalaryPayStrategy {
	public void pay(PaymentContext ctx) { 
		 System.out.println("现在给"+ctx.getUserName()+"人民币现金支付"+ctx.getMoney()+"元"); }
}
package Salary;

public class DollarCash implements SalaryPayStrategy {
	public void pay(PaymentContext ctx) {
		 System.out.println("现在给"+ctx.getUserName()+"美元现金支付"+ctx.getMoney()+"元");
		 }
}
package Salary;

public class Card implements SalaryPayStrategy {

	@Override
	public void pay(PaymentContext ctx) {
		 PaymentContext2 ctx2 = (PaymentContext2)ctx;
		 System.out.println("现在给"+ctx2.getUserName()+
				 "的"+ctx2.getAccount()+"帐号支付"+ctx2.getMoney()+"元");
		 }
}
package Salary;

public class PaymentContext {
	private String userName;
	private double money;
	SalaryPayStrategy strategy=null;
	public PaymentContext(String userName,double money,SalaryPayStrategy strategy){
		this.userName = userName;
		this.money = money;
		this.strategy = strategy;
	}
	public String getUserName(){
		return userName;
	}
	public double getMoney(){
		return money;
	}
	public void payNow(){
		this.strategy.pay(this);
	}
}
package Salary;

public class PaymentContext2 extends PaymentContext {
	private String account;

	public PaymentContext2(String userName, double money,
			SalaryPayStrategy strategy, String account) {
		super(userName, money, strategy);
		this.account = account;
	}

	public String getAccount() {
		return account;
	}
	
}
package Salary;

public class test {

	public static void main(String[] args) {
		SalaryPayStrategy strategyRMB = new RMBCash();
		SalaryPayStrategy strategyDollar = new DollarCash();
		SalaryPayStrategy strategyCard = new Card();
		PaymentContext ctx1 = new PaymentContext("小王",6000,strategyRMB);
		ctx1.payNow();
		PaymentContext ctx2 = new PaymentContext("Petter",8000,strategyDollar);
		ctx2.payNow();
		PaymentContext ctx3 = new PaymentContext2("小张",10000,strategyCard,"0123456789");
		ctx3.payNow();
	}

}

通过扩展上下文类,新加一个上下文类并继承于上一个上下文类来增加卡号这一属性,来实现功能的扩展,这样的优点也很明显。

所有策略的实现风格更统一,策略需要的数据都统一从上下文来获取,这样在使用方法上也很统一;
在上下文中添加新的数据,别的相应算法也可以用得上,可以视为公共的数据

但也有相应的缺点

如果只有一个特定的算法来使用这些数据,那么这些数据有些浪费;另外每次添加新的算法都去扩展上下文,容易形成复杂的上下文对象层次。

那,如果不扩展上下文,而是通过增加具体的算法类呢?
我们来看一下代码

public class Card2 implements PaymentStrategy{
 private String account = "";
 //构造方法,传入帐号信息
 public Card2(String account){
 this.account = account;
 }
 public void pay(PaymentContext ctx) {
 System.out.println("现在给"+ctx.getUserName()+"的"
+this.account+"帐号支付了"+ctx.getMoney()+"元");
 } }

虽然只通过增加一个类便完成了功能的实现,但是它的缺点也显而易见。

首先,它跟其它策略实现的风格不一致
其次,外部使用这些策略算法的时候也不一样了,不太好以一个统一的方式来动态切换策略算法

要是说他有什么优点的话,那就是比较好想,实现简单。

在实际应用中还是要具体问题具体分析,但是一般更多的采用扩展上下文类的方法。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值