策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模式的结构
策略模式涉及到三个角色:
- 环境(Context)角色:持有一个Strategy类的引用。
- 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。
public class Context {
private Strategy strategy;
/**
* 策略方法
*/
public void contextInterface() {
strategy.strategyInterface();
}
}
abstract public class Strategy {
/**
* 策略方法
*/
public abstract void strategyInterface();
}
public class ConcreteStrategy extends Strategy {
/**
* 策略方法
*/
public void strategyInterface() {
// write you algorithm code here
}
}
示意性源码仅仅是策略模式的最小实现,因此具体策略角色才只有一个。一般而言,有意义的策略模式的应用都会涉及到多于一个的具体策略角色。
例子:图书折扣的计算
算法一(NoDiscountStrategy):对有些图书没有折扣。折扣算法对象返还0作为折扣值。
算法二(FlatRateStrategy):对有些图书提供一个固定量值为1元的折扣。
算法三(PercentageStrategy):对有些图书提供一个百分比的折扣,比如一本书价格为20元,折扣百分比为7%,那么折扣值就是20*7%=1.4元。
抽象折扣类 DiscountStrategy
本类给出的接口是供具体子类实现的,在使用具体折扣类时,客户端应当调用构造子并且传入图书的单价、购买的册数,然后调用calculateDiscount()方法得到折扣总额。
abstract public class DiscountStrategy {
private double price = 0;
private int copies = 0;
/**
* 策略方法
*/
public abstract double calculateDiscount();
/**
* 构造子
*/
public DiscountStrategy(double price, int copies) {
this.price = price;
this.copies = copies;
}
}
具体折扣类 NoDiscountStrategy
对任何输入都给出0作为折扣值,这表明对该类图书没有折扣。
public class NoDiscountStrategy extends DiscountStrategy {
private double price = 0;
private int copies = 0;
/**
* 构造子
*/
public NoDiscountStrategy(double price, int copies) {
super(price, copies);
this.price = price;
this.copies = copies;
}
/**
* 策略方法
*/
public double calculateDiscount() {
return 0;
}
}
具体折扣类 FlatRateStrategy
amount为固定的折扣值,在计算总折扣值之前需要设置,默认为0。
public class FlatRateStrategy extends DiscountStrategy {
private double price = 0;
private int copies = 0;
private double amount;
/**
* 构造子
*/
public FlatRateStrategy(double price, int copies) {
super(price, copies);
this.price = price;
this.copies = copies;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
/**
* 策略方法
*/
public double calculateDiscount() {
return copies * amount;
}
}
具体折扣类PercentageStrategy
percent为的折扣比例,在计算总折扣值之前需要设置,默认为0。
public class PercentageStrategy extends DiscountStrategy {
private double price = 0;
private int copies = 0;
private double percent;
/**
* 构造子
*/
public PercentageStrategy(double price, int copies) {
super(price, copies);
this.price = price;
this.copies = copies;
}
public double getPercent() {
return percent;
}
public void setPercent(double percent) {
this.percent = percent;
}
/**
* 策略方法
*/
public double calculateDiscount() {
return copies * price * percent;
}
}
环境角色类 Context
public class Context {
private DiscountStrategy strategy;
public Context(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double executeStrategy() {
return strategy.calculateDiscount();
}
}
客户端实现
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new NoDiscountStrategy(20, 10));
System.out.println("discount = " + context.executeStrategy());
FlatRateStrategy strategy2 = new FlatRateStrategy(20, 10);
strategy2.setAmount(1);
context = new Context(strategy2);
System.out.println("discount = " + context.executeStrategy());
PercentageStrategy strategy3 = new PercentageStrategy(20, 10);
strategy3.setPercent(0.07);
context = new Context(strategy3);
System.out.println("discount = " + context.executeStrategy());
}
}
值得注意的是:
应当由客户端决定在什么情况下使用什么具体策略角色,策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法。
策略模式不适合于处理同时嵌套多于一个算法的情形。策略模式只适用于客户端在几种算法中选择一种的情形,并不适用于客户端同时需要几种算法的情形。
在什么情况下应当使用策略模式
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 一个系统的算法使用的数据不可以让客户端知道。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
策略模式的优缺点
优点:
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式造成很多的策略类。