策略模式
策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
使用策略模式可以把行为和环境分割开来。环境类负责维持和查询行为类,各种算法则在具体策略类( ConcreteStrategy) 中提供。
由于算法和环境独立开来,算法的增减、修改都不会影响环境和客户端。当出现新的促销折扣或现有的折扣政策出现变化时,只需要实现新的策略类,并在客户端登记即可。策略模式相当于"可插入式(Pluggable)的算法"。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图
策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象管理。准备一组算法,并将每一个算法封装起来,使得它们可以互换。
优点:
- 策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。
- 避免使用难以维护的多重条件选择语句。
- 更好的扩展。
缺点:
- 上述策略模式,把分支判断又放回到客户端,要改变需求算法时,还是要去更改客户端的程序。
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
- 增加了对象的数目。
- 只适合扁平的算法结构。
本质
分离算法,选择实现。
涉及角色
- 环境(Context) 角色:持有一个Strategy类的引用(上下文对象),负责和具体的策略类交互。
- 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(ConcreteStrategy) 角色:包装了相关的算法或行为。
使用场景:
- 出现同关个算法,有很多不同的实现的情况,一个系统需要动态地在几种算法中选择一种,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次
- 出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况,可以使用策略模式来代替这些条件语句
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
模式相关
- 策略模式功能:把具体算法从具体业务处理中独立
- 策略模式与if-else语句:多个if-else出现考虑使用策略模式
- 算法的平等性:策略算法是形同行为的不同实现
- 谁来选择具体策略算法:客户端;由上下文来选择具体的策略算法
UML图
收银软件的策略模式实现代码及UML图
package Cash;
public abstract class CashSuper {
public abstract double acceptCash(double money);
}
-----------------------------------------------
package Cash;
public class CashNormal extends CashSuper {
正常收费,原价返回
@Override
public double acceptCash(double money) {
return money;
}
}
------------------------------------------------
package Cash;
public class CashRebate extends CashSuper {
//
//打折收费,初始化时,必需要输入折扣率,如八折,就是0.8
private double moneyRebate=1;
public CashRebate(double moneyRebate)
{
this.moneyRebate=moneyRebate;
}
@Override
public double acceptCash(double money) {
return money*moneyRebate;
}
}
-----------------------------------------------------
package Cash;
public class CashReturn extends CashSuper {
//返利收费,初始化时必需输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100
private double moneyCondition=0.0;
private double moneyReturn=0.0;
public CashReturn(double moneyCondition,double moneyReturn){
this.moneyCondition=moneyCondition;
this.moneyReturn=moneyReturn;
}
@Override
public double acceptCash(double money) {
double result=money;
if(money>=moneyCondition)
result=money-Math.floor(money/moneyCondition)*moneyReturn;
return result;
}
}
-------------------------------------------------------------
package Cash;
//仅策略模式
public class CashContext {
private CashSuper cs;
public CashContext(CashSuper csuper)
{
this.cs=csuper;
}
public double GetResult(double money)
{
return cs.acceptCash(money);
}
}
---------------------------------------------------------------
package Cash;
public class Client {
//仅策略模式
public static void main(String[] args) {
CashContext cc=null;
double money=1000;
String type="打8折";
switch(type)
{
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;
}
System.out.println(cc.GetResult(money));
}
}
深入:
- 在策略模式中,通常是上下文使用具体的策略实现对象,反过来,策略实现对象也可以从上下文获取所需要的数据,因此可以将上下文当参数传递给策略实现对象
- 在这种情况下,上 下文封装着具体策略对象进行算法运算所需要的数据,具体策略对象通过回调上下文的方法来获取这些数据。
两种方式实现策略模式
1.扩展上下文的方式
优点:
●所有策略的实现风格更统一,策略需要的数据都统一从上下文来获取,这样在使用方法上也很统一;
●在上下文中添加新的数据,别的相应算法也可以用得上,可以视为公共的数据。
缺点:
●如果只有一个特定的算法来使用这些数据,那么这些数据有些浪费;
●另外每次添加新的算法都去扩展上下文,容易形成复杂的上下文对象层次。
2.在策略算法的实现上添加自己需要的数据的方式
优点:
●比较好想,实现简单
缺点:
●跟其它策略实现的风格不一致。
●外部使用这些策略算法的时候也样了,不太好以一个统一的方式来动态切换策略算法。