策略模式
策略模式本质
分离算法,选择实现
对设计原理的实现
从设计原则上来看,策略模式很好地体现了开-闭原则。通过把一系列可变的算法进行封装,并定义出合理的使用结构,使得在系统出现新算法时,额能很容易的把算法加入到已有的系统中,而已有的实现不需要做任何更改。
何时选择策略模式
- 出现许多相关的类,仅仅是行为有差别的情况下,可以使用策略模式来使用多个行为中的一个配置一个类的方法,实现算法动态切换。
- 出现同一个算法,有很多不同实现的情况下,可以使用策略模式来把这些“不同的实现”实现成为一个算法的类层次。
- 需要封装算法中,有与算法相关数据的情况下,可以使用策略模式避免暴露这些跟算法相关的数据结构。
- 出现抽象一个定义了很多行为的类,并且是通过多个if-else语句来选择这些行为的情况下,可以使用策略模式来代替这些条件语句。
优缺点
优点:
- 定义一系列算法
- 避免多重条件语句
- 更好的扩展性
缺点
- 客户必须了解每种策略的不同
- 怎讲对象数目
- 只适合扁平的算法结构(一系列算法的地位是平等的,是可以相互替换的,在运行时刻只有一个算法被使用,限制了算法的使用层级,使用的时候不能嵌套使用)
示例:工资支付的实现思路
功能:工资支付方式的问题。很多企业的工资支付方式是很灵活的,可支付的方式是很多的,比如:人民币现金支付、美元现金支付、银行转账到工资账号、银行转账到工资卡,甚至可能工资转股权等方式。
要实现这样的功能,策略模式是一个很好的选择,在实现这个功能时,不同的策略算法需要的数据是不一样的,如:现金支付就不需要银行账号,而银行转账就需要账号。这就导致在设计策略接口中的方法时,不太好确定参数的个数,而且,就算现在吧所有参数都列上了,今后扩展呢?难道再来修改策略接口吗?那到底如何实现才能扩展时最方便呢?
解决方案一:就是把上下文(context)当作参数传递给策略对象。这样,如果要扩展新的策略实现,只需要扩展上下文就可以了,已有的实现不需要做任何更改。
实现代码:
/**
* 支付工资的策略接口,公司有多种支付工资的算法
* 比如:现金,银行卡,美元支付等
**/
public interface PaymentStrategy{
//公司给某人真正支付工资
//@param ctx 支付工资的上下文,里面包含算法需要的数据
public void pay(PaymentContext ctx);
}
//人民币现金支付
public class RMBCash implements PaymentStrategy{
public void pay(PaymentContext ctx){
System.out.println("现金给"+ctx.getUserName()+"人民币现金支付"+ctx.getMoney()+"元");
}
}
//美元现金支付
public class DollarCash implements PaymentStrategy{
public void pay(PaymentContext ctx){
System.out.println("现金给"+ctx.getUserName()+"美元现金支付"+ctx.getMoney()+"元");
}
}
//支付工资的上下文,每个人的工资不同,支付方式也不同
public class PaymentContext{
private String userName = null;
private double money = 0.0;
//支付工资的方式的策略接口
private PaymentStrategy strategy = null;
//构造函数,传入被支付工资的人员,应支付的金额和具体的支付策略
//userName 被支付工资的人员 money 应支付的金额 strategy 具体的支付策略
public PaymentContext (String userName, double money, PaymentStrategy strategy){
this.userName = userName;
this.money = money;
this.strategy = strategy;
}
//省略get方法,只有get方法,让策略算法在实现的时候,根据需要来获取上下文的数据
//立即支付工资
public void payNow(){
//使用客户希望的支付策略来支付工资
this.strategy.pay(this);
}
}
//客户端
public class Client{
public static void main(String[] agrs){
//创建相应的支付策略
PaymentStrategy strategyRMB = new RMBCash();
PaymentStrategy strategyDollar = new DollarCash();
//准备小李的支付工资上下文
PaymentContext ctx1 = new PaymentContext ("小李",5000,strategyRMB);
//向小李支付工资
ctx1.payNow();
//切换一个人,给petter支付工资
PaymentContext ctx2 = new PaymentContext ("petter",8000,strategyDollar);
ctx2.payNow();
}
}
扩展示例
现在我们要加一种支付方式,要求能支付到银行卡
代码示例:
//扩展的支付上下文对象
public class PaymentContext2 extends PaymentContext{
//银行账号
private String account = null;
//构造函数
public PaymentContext2 (String userName, double money, String account, PaymentStrategy strategy){
super(userName,money, strategy);
this.account = account;
}
public String getAccount(){return account;}
}
public class Card implements PaymentStrategy{
public void pay(PaymentContext ctx){
PaymentContext2 ctx2 = (PaymentContext2)ctx;
System.out.println("现在给"+ctx2.getUserName()+"的"+ctx2.getAccount()+"账号支付了"+ctx.getMoney()+"元");
}
}
public class Client {
public static void main(String[] agrs){
PaymentStrategy strategyCard = new Card();
PaymenttContext ctx= new PaymentContext2("小王",9000,"00121113",strategyCard);
ctx.payNow();
}
}
另一种扩展方式:
上面那种方式是通过上下文对象来准备新的算法需要的数据,还有一种就是通过策略的构造函数来传入新算法需要的数据,这样实现就不需要扩展上下文了,直接添加一个新的算法实现就可以了。
代码示例:
public class Card2 impements PaymentStrategy{
//账号信息
private String account = "";
//构造函数
public Card2(String account){
this.account = account;
}
public void pay(PaymentContex ctx){
System.out.println("现在给"+ctx2.getUserName()+"的"+ctx2.getAccount()+"账号支付了"+ctx.getMoney()+"元");
}
}
两种实现扩展方式的比较:
对与扩展上下文的方式
优点:
- 这样实现,使所有策略的风格更统一;
- 在上下文添加的数据可以视为公共数据,别的相应算法也能用上;
缺点:
1. 如果这些数据只是用于特定的算法,这样数据就有点浪费;
2. 每次添加新的算法都去扩展上下文,容易造成复杂的上下文对象层次;
对与在策略算法的实现上添加自己需要的数据方式
优点:
1. 实现简单
缺点:
1. 和其他策略算法的实现方式不一样
2. 外部使用这个策略算法的时候也不一样,难以以一个统一的方式来动态切换策略算法。