简介
策略是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法对象能够被替换。
场景
你在开发一款导航应用,类似高德。你要实现自动路线规划的功能:希望输入地址后就能在地图上看到前往目的地的最快路线。
程序的首个版本只能规划公路路线,只适合自驾的人。为了尽量向高德靠拢,你想添加规划步行路线的功能。之后,可能还要添加规划公交地铁路线的功能。过不久可能还要给骑行者规划路线。又过了一段时间,可能还要给城市中的所有景点提供游览路线。
这时你发现,每次添加新的路线规划算法后,导航应用中主要类的体积就会增加一倍。代码就越来越难维护。
无论是修复简单缺陷还是微调街道权重,对某个算法进行任何修改都会影响整个类,从而增加了在已有正常代码中引入错误的风险。
另外,团队合作也非常低效。如果应用由一个团队开发,开发者会在合并冲突的工作上花费很长时间。在实现新功能的过程中,团队需要修改同一个巨大的类,很容易出现冲突。
解决
策略模式建议找出用不同方式完成特定任务的类,然后把里面的算法抽取到一组叫做策略的独立类里。
名叫上下文的原始类必须包含一个成员变量来存储对于每种策略的引用。上下文并不执行任务,而是把工作委派给引用的策略对象。
上下文不负责选择符合任务需要的算法——客户端会把所需策略传递给上下文。实际上,上下文并不了解策略细节,它会通过同样的通用接口跟所有策略交互,这个接口只需要暴露一个方法供上下文调用,具体策略会在这个方法中封装特定算法。
因此,上下文可以独立于具体策略。这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。
在导航应用里,每个路线规划算法都可以被抽取到 只有一个buildRoute生成路线方法的 独立类里。这个方法接收起点和终点作为参数,并返回路线途径点的集合。
就算传递给每个路径规划类的参数一模一样,它创建的路线也可能完全不同。导航类的主要工作是在地图上渲染一系列中途点,不会在意如何选择算法。这个类中还有一个用于切换当前路径规划策略的方法,因此客户端(例如用户界面中的切换按钮)可用其他策略替换当前选择的路径规划行为。
代码
// Strategy 接口
interface NavigationStrategy {
List<String> buildRoute(String start, String end); // 路由算法接口
}
// 具体策略实现
class WalkingStrategy implements NavigationStrategy {
@Override
public List<String> buildRoute(String start, String end) { // 步行策略
return List.of(start, "→ Central Park", "→ 5th Ave", end); // 步行路线
}
}
class BusTourStrategy implements NavigationStrategy {
@Override
public List<String> buildRoute(String start, String end) { // 巴士路线策略
return List.of(start, "→ Bus Stop A", "→ Bus Terminal", end); // 公交路线
}
}
class HelicopterStrategy implements NavigationStrategy {
@Override
public List<String> buildRoute(String start, String end) { // 飞行路线策略
return List.of(start, "→ Airport", "→ "+end+" Airspace", end); // 空中路线
}
}
// 上下文类
class TourGuide {
private NavigationStrategy strategy;
public void setStrategy(NavigationStrategy strategy) { // 运行时切换策略
this.strategy = strategy;
}
public void showRoute(String start, String end) { // 统一入口方法
if(strategy == null) {
System.out.println("请先选择导览策略");
return;
}
List<String> checkpoints = strategy.buildRoute(start, end); // 委托策略执行
System.out.println("建议路线:");
checkpoints.forEach(point -> System.out.println("▪ " + point));
}
}
// 客户端示例
public class Client {
public static void main(String[] args) {
TourGuide app = new TourGuide();
Scanner input = new Scanner(System.in);
System.out.println("请选择导览模式:\n1.步行 2.巴士 3.直升机");
int choice = input.nextInt(); // 动态选择策略
switch(choice) {
case 1:
app.setStrategy(new WalkingStrategy()); // 绑定步行策略
break;
case 2:
app.setStrategy(new BusTourStrategy());
break;
case 3:
app.setStrategy(new HelicopterStrategy());
break;
default:
throw new IllegalArgumentException("无效选择");
}
app.showRoute("时代广场", "自由女神像"); // 执行所选策略
}
}
关键实现细节
- 策略切换机制:通过setStrategy方法动态绑定策略对象
- 算法封装:每个具体策略类自行实现路径生成算法
- 正交扩展:新增导航策略无需修改现有代码(如可添加BicycleStrategy)
- 接口统一:所有策略实现相同的buildRoute方法签名
总结
- 上下文(Context)维护指向具体策略的引用,且只通过策略接口与策略对象进行交流。
- 策略(Strategy)接口是所有具体策略的通用接口,它声明了一个上下文用于执行策略的方法。
- 具体策略(Concrete Strategies)实现了上下文所用算法的各种不同变体。
- 当上下文需要运行算法时,它会调用策略对象的方法。上下文不清楚它所涉及的策略类型与算法的执行方式。
- 客户端(Client)会创建一个特定策略对象并把他传递给上下文。上下文会提供一个设置器以便客户端在运行时替换相关联的策略。