策略模式(Strategy Pattern)定义了一系列算法,把这些算法一个个封装成单独的类,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
策略模式重点是封装不同的算法和行为,不同的场景下可以相互替换。策略模式是开闭原则的体现——对扩展开放,对修改关闭:新增策略时不影响其他类,且场景类只依赖抽象而不依赖具体实现。
示例:字符串替换策略
这里以字符串替换为例:读取一个文件后,需要替换其中的变量再输出。替换方式可能有多种,我们用策略模式来支持运行时切换。
首先,建立抽象策略类 RepTempRule,定义公用变量和方法:
public abstract class RepTempRule {
protected String oldString = "";
public void setOldString(String oldString) {
this.oldString = oldString;
}
protected String newString = "";
public String getNewString() {
return newString;
}
public abstract void replace() throws Exception;
}
现在有两个具体策略:将文本中的 aaa 替换成 bbbb,或替换成 ccc:
public class RepTempRuleOne extends RepTempRule {
@Override
public void replace() throws Exception {
newString = oldString.replaceFirst("aaa", "bbbb");
System.out.println("this is replace one");
}
}
public class RepTempRuleTwo extends RepTempRule {
@Override
public void replace() throws Exception {
newString = oldString.replaceFirst("aaa", "ccc");
System.out.println("this is replace two");
}
}
算法解决类,提供运行时自由选择和切换算法的能力:
public class RepTempRuleSolve {
private RepTempRule strategy;
public RepTempRuleSolve(RepTempRule rule) {
this.strategy = rule;
}
public String getNewContext(String oldString) throws Exception {
strategy.setOldString(oldString);
strategy.replace();
return strategy.getNewString();
}
public void changeAlgorithm(RepTempRule newAlgorithm) {
strategy = newAlgorithm;
}
}
客户端调用:
public class Client {
public void testReplace() throws Exception {
String context = "aaa is a placeholder";
// 使用第一套替代方案
RepTempRuleSolve solver = new RepTempRuleSolve(new RepTempRuleOne());
System.out.println(solver.getNewContext(context));
// 运行时切换到第二套方案
solver.changeAlgorithm(new RepTempRuleTwo());
System.out.println(solver.getNewContext(context));
}
}
这样就达到了在运行期间自由切换算法的目的。Strategy 的核心在于抽象类的使用,使用策略模式可以在用户需要变化时,修改量很少,而且快速。
优点
- 可替换继承关系:将算法封装在独立的 Strategy 类中,使得算法可以独立于 Context 改变,易于切换、易于理解、易于扩展。相比继承,算法不会被硬编码进 Context。
- 消除条件语句:Strategy 模式提供了替代
if-else条件分支选择行为的方案。当不同行为堆砌在一个类中时很难避免条件语句,将行为封装到独立 Strategy 类中可以消除这些条件语句。 - 实现的选择:Strategy 模式可以提供相同行为的不同实现,客户可以根据时间/空间权衡取舍从不同策略中进行选择。
缺点
- 客户端需要了解所有策略类:客户端必须知道各策略的区别并自行决定使用哪一个,可能需要向客户暴露具体的实现细节。
- 通信开销:无论 ConcreteStrategy 实现的算法简单还是复杂,都共享 Strategy 定义的接口。某些简单策略可能不会用到接口传递的所有参数,导致 Context 创建了一些永远不会被用到的参数。
- 策略类数量增多:每个策略都是一个类,策略较多时会产生大量类。可以通过享元模式在一定程度上减少对象数量。
适用场景
- 以不同的格式保存文件
- 以不同的算法压缩文件
- 以不同的算法截获图像
- 以不同的格式输出同样数据的图形(如曲线或柱状图)
Strategy 和 Factory 有一定类似,但 Strategy 相对简单易理解,且可以在运行时刻自由切换;Factory 的重点是创建对象。
参考: