策略模式
策略模式:它定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
我们继续以例子来阐述该模式
假如商场搞促销,有多种促销方式,促销方式包括:满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()+"元");
} }
虽然只通过增加一个类便完成了功能的实现,但是它的缺点也显而易见。
首先,它跟其它策略实现的风格不一致
其次,外部使用这些策略算法的时候也不一样了,不太好以一个统一的方式来动态切换策略算法
要是说他有什么优点的话,那就是比较好想,实现简单。
在实际应用中还是要具体问题具体分析,但是一般更多的采用扩展上下文类的方法。