引言:从现实世界到代码世界的面向对象
在商业策略制定中,企业会根据市场环境选择不同的竞争策略;在军事行动中,指挥官会根据敌情选择不同的战术;在游戏对战中,玩家会根据局势调整作战方式。这种根据情境选择不同行为的模式,在软件设计中同样普遍存在。策略模式(Strategy Pattern)正是为解决这类问题而生的经典设计模式。
想象你正在使用导航软件规划路线。同一个目的地,你可以选择:
-
最快路线:优先考虑时间
-
最短距离:不考虑路况,只求距离最短
-
避开高速:宁愿慢些也要省过路费
-
经济路线:平衡时间和费用
导航软件如何优雅地实现这些不同的路线计算算法?这就是策略模式大显身手的地方。作为行为型设计模式的代表,策略模式让我们能够定义一系列算法,并将每个算法封装起来,使它们可以相互替换。
一、策略模式解决了什么问题?
先看一个常见的反例:
public class Navigator {
public void buildRoute(String routeType) {
if ("FASTEST".equals(routeType)) {
// 复杂的最快路线算法
System.out.println("计算最快路线...考虑实时路况");
} else if ("SHORTEST".equals(routeType)) {
// 最短距离算法
System.out.println("计算最短距离...忽略路况");
} else if ("ECONOMIC".equals(routeType)) {
// 经济路线算法
System.out.println("计算经济路线...平衡时间和费用");
}
// 每新增一种路线类型,就要修改这里
}
}
这种实现方式存在几个明显问题:
-
违反开闭原则:新增算法需要修改原有类
-
代码臃肿:所有算法堆积在一个类中
-
难以维护:随着算法增加,条件判断越来越复杂
-
复用困难:相同算法难以在其他地方复用
策略模式正好可以解决这些问题。
二、策略模式的核心结构
策略模式在GoF《设计模式》中的正式定义:
定义一系列算法,将每个算法封装起来,并使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。
策略模式包含三个核心角色:
-
Context(上下文):维护对策略对象的引用,负责将客户端请求委托给当前策略
-
Strategy(策略接口):定义所有支持算法的公共接口
-
ConcreteStrategy(具体策略):实现策略接口的具体算法类
1. 基础实现
// 策略接口
interface RouteStrategy {
void buildRoute(Point start, Point end);
}
// 具体策略:最快路线
class FastestRouteStrategy implements RouteStrategy {
@Override
public void buildRoute(Point start, Point end) {
System.out.println("计算最快路线...考虑实时路况");
// 实际算法实现
}
}
// 具体策略:最短距离
class ShortestRouteStrategy implements RouteStrategy {
@Override
public void buildRoute(Point start, Point end) {
System.out.println("计算最短距离...忽略路况");
// 实际算法实现
}
}
// 上下文
class Navigator {
private RouteStrategy strategy;
public void setStrategy(RouteStrategy strategy) {
this.strategy = strategy;
}
public void buildRoute(Point start, Point end) {
if (strategy != null) {
strategy.buildRoute(start, end);
} else {
System.out.println("请先设置路线策略");
}
}
}
// 使用示例
public class Client {
public static void main(String[] args) {
Navigator navigator = new Navigator();
// 使用最快路线策略
navigator.setStrategy(new FastestRouteStrategy());
navigator.buildRoute(pointA, pointB);
// 切换为最短距离策略
navigator.setStrategy(new ShortestRouteStrategy());
navigator.buildRoute(pointA, pointB);
}
}
三、Spring中的策略模式实践
在Spring应用中,我们可以利用DI(依赖注入)更加优雅地实现策略模式:
// 策略接口
public interface DiscountStrategy {
BigDecimal applyDiscount(BigDecimal amount);
String getStrategyName();
}
// 具体策略:会员折扣
@Service
public class MemberDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal applyDiscount(BigDecimal amount) {
return amount.multiply(new BigDecimal("0.9"));
}
@Override
public String getStrategyName() {
return "MEMBER_DISCOUNT";
}
}
// 具体策略:节日折扣
@Service
public class FestivalDiscountStrategy implements DiscountStrategy {
@Override
public BigDecimal applyDiscount(BigDecimal amount) {
return amount.multiply(new BigDecimal("0.8"));
}
@Override
public String getStrategyName() {
return "FESTIVAL_DISCOUNT";
}
}
// 策略上下文
@Service
public class DiscountContext {
private final Map<String, DiscountStrategy> strategyMap;
@Autowired
public DiscountContext(List<DiscountStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(
DiscountStrategy::getStrategyName,
Function.identity()
));
}
public BigDecimal applyDiscount(String strategyName, BigDecimal amount) {
DiscountStrategy strategy = strategyMap.get(strategyName);
if (strategy == null) {
throw new IllegalArgumentException("未知的折扣策略");
}
return strategy.applyDiscount(amount);
}
}
// 控制器使用
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private DiscountContext discountContext;
@PostMapping("/checkout")
public ResponseEntity<BigDecimal> checkout(
@RequestParam String discountType,
@RequestParam BigDecimal amount) {
BigDecimal finalAmount = discountContext.applyDiscount(discountType, amount);
return ResponseEntity.ok(finalAmount);
}
}
Spring策略模式优势:
-
自动收集所有策略实现
-
策略与上下文完全解耦
-
方便扩展新策略
-
策略可以享受Spring的所有特性(AOP、依赖注入等)
四、策略模式的性能考量
4.1 策略对象的创建成本
对于高频调用的策略,需要考虑策略对象的创建方式:
-
每次创建新实例:简单但可能产生GC压力
-
策略对象复用:适合无状态的策略
-
对象池技术:适用于创建成本高的策略
4.2 策略选择的效率
策略选择方式 | 时间复杂度 | 适用场景 |
---|---|---|
if-else/switch | O(n) | 策略数量少(5个以下) |
Map查找 | O(1) | 策略数量多 |
策略链 | O(n) | 需要依次尝试策略 |
五、策略模式的优缺点分析
优势
-
开闭原则:无需修改已有代码即可新增策略
-
消除条件语句:替代大量的if-else或switch-case
-
提高可测试性:每个策略可以独立测试
-
运行时灵活性:支持动态切换算法
-
代码复用:不同上下文可以共享策略
局限性
-
客户端必须了解策略:需要知道不同策略的区别
-
策略类增多:可能增加系统中类的数量
-
通信开销:策略与上下文可能需要交换数据
-
不适合简单算法:可能会过度设计简单场景
六、策略模式与其他模式的关系
6.1与工厂模式的区别
模式 | 关注点 | 应用阶段 | 主要作用 |
---|---|---|---|
工厂模式 | 对象创建 | 初始化阶段 | 隐藏创建逻辑 |
策略模式 | 行为算法 | 运行时 | 封装可互换的行为 |
6.2 与状态模式的对比
相似点:都有上下文和多个实现类
不同点:
-
状态模式:状态转换通常由上下文内部管理
-
策略模式:策略选择通常由客户端控制
最后,策略模式不仅仅是一种编码技巧,它体现了"分而治之"的古老智慧。通过将复杂多变的行为分解为独立的策略单元,我们获得了:
-
清晰的边界:每个策略只关注自己的算法
-
灵活的组合:可以像乐高积木一样搭配策略
-
可控的变化:算法变化被隔离在策略内部
正如《孙子兵法》所言:"兵无常势,水无常形",软件设计也应该能够随需而变。策略模式正是帮助我们实现这种灵活性的利器。
设计模式的最高境界是"无招胜有招"------不是机械地套用模式,而是理解其思想后自然地融入设计中。