1 解决的问题
一天, 你打算为游客们创建一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。
用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。
程序的首个版本只能规划公路路线。 驾车旅行的人们对此非常满意。 但很显然, 并非所有人都会在度假时开车。 因此你在下次更新时添加了规划步行路线的功能。 此后, 你又添加了规划公共交通路线的功能。
而这只是个开始。 不久后, 你又要为骑行者规划路线。 又过了一段时间, 你又要为游览城市中的所有景点规划路线。

尽管从商业角度来看, 这款应用非常成功, 但其技术部分却让你非常头疼: 每次添加新的路线规划算法后, 导游应用中主要类的体积就会增加一倍。 终于在某个时候, 你觉得自己没法继续维护这堆代码了。
无论是修复简单缺陷还是微调街道权重, 对某个算法进行任何修改都会影响整个类, 从而增加在已有正常运行代码中引入错误的风险。
此外,团队合作将变得低效。如果你在应用成功发布后招募了团队成员,他们会抱怨在合并冲突的工作上花费了太多时间。在实现新功能的过程中,你的团队需要修改同一个巨大的类,这样他们所编写的代码相互之间就可能会出现冲突。
2 策略模式
将不同的策略 or 算法独自封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。这里的算法,相当于平时的业务逻辑。

3 实现代码
抽象策略:实现两个数字之间的运算。
具体策略:相加运算、相减运算、相乘运算。
客户端:创建一个特定策略对象并传递给上下文。上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。
上下文:维护具体策略的引用,并通过策略接口与该对象进行交流。
先是 策略的定义:
// 计算策略接口
public interface ICompute {
String compute(int first, int second);
}
// 具体策略
public class AddCompute implements ICompute {
@Override public String compute(int first, int second) {
return "计算:" + first + " + " + second + " = " + (first + second);
}
}
public class SubCompute implements ICompute {
@Override public String compute(int first, int second) {
return "计算:" + first + " - " + second + " = " + (first - second);
}
}
public class MulCompute implements ICompute {
@Override public String compute(int first, int second) {
return "计算:" + first + " * " + second + " = " + (first * second);
}
}然后是 策略的创建和使用:
public class Context {
private ICompute compute;
public Context() { this.compute = new AddCompute(); }
public void setCompute(ICompute compute) { this.compute = compute; }
public void calc(int first, int second) {
System.out.println(compute.compute(first, second));
}
}
// 测试用例
public class TestCompute {
public static void main(String[] args) {
Context context = new Context();
context.setCompute(new AddCompute());
context.calc(4, 2);
context.setCompute(new SubCompute());
context.calc(4, 2);
context.setCompute(new MulCompute());
context.calc(4, 2);
}
}但,这其实没有发挥策略模式的优势,而是退化成了:面向对象的多态或基于接口而非实现编程,非动态,直接在代码中指定了使用哪种策略。
而实际开发中场景更多的是:事先并不知道会使用那种策略,而是在程序运行期间,根据配置、用户输入、计算记过等不确定因素,动态地决定使用那种策略。对于上述这种 无状态的(不包含成员变量,只是纯粹的算法实现),策略对象可以共享的场景,可以把 Context 改写为工厂类的实现方式:
public class Context {
private static final Map<String, ICompute> computes = new HashMap<>();
static {
computes.put("+", new AddCompute());
computes.put("-", new SubCompute());
computes.put("*", new MulCompute());
computes.put("/", new DivCompute());
}
public void calc(String operator, int first, int second) {
System.out.println(computes.get(operator).compute(first, second));
}
}
// 修改后的测试用例
public class TestCompute {
public static void main(String[] args) {
Context context = new Context();
context.calc("+", 4, 2);
context.calc("-", 4, 2);
context.calc("*", 4, 2);
context.calc("/", 4, 2);
}
}4 优缺点
优点
- 动态切换对象内的算法;
- 将算法的实现与使用算法的代码隔离开;
- 开闭原则。
缺点
- 客户端必须知晓策略间的不同—他需要选择合适的策略;
- 小型的策略,不如函数式编程简洁 (匿名函数实现不同版本算法);
- 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁。
5 应用场景
- 系统需要动态地切换几种算法;
- 多重条件选择语句,想对分支判断进行隐藏,可用策略模式把行为转移到具体策略类中;
- 只希望客户端直接使用已封装好算法,而不用关心算法的具体实现细节;
6 经典应用例子
Java 8 开始支持 lambda 方法, 它可作为一种替代策略模式的简单方式。
这里有一些核心 Java 程序库中策略模式的示例:
- 对
java.util.Comparator#compare()的调用来自Collections#sort(). javax.servlet.http.HttpServlet: service()方法, 还有所有接受HttpServletRequest和HttpServletResponse对象作为参数的doXXX()方法。javax.servlet.Filter#doFilter()