设计模式(二十二)行为型:策略模式详解
设计模式(二十二)行为型:策略模式详解
策略模式(Strategy Pattern)是 GoF 23 种设计模式中最具实用性和广泛影响力的行为型模式之一,其核心价值在于定义一系列算法或行为,并将每个算法封装到独立的类中,使得它们可以相互替换,且算法的变化独立于使用它的客户端。它通过将“算法”与“使用算法的上下文”解耦,实现了行为的动态配置与高度可扩展性。策略模式是构建可配置系统、实现多态行为、支持插件化架构、优化性能选择、实现业务规则引擎、支持 A/B 测试等场景的基石,是将“算法即服务”理念落地的关键设计范式。
一、详细介绍
策略模式解决的是“一个类有多种实现同一功能的算法,且这些算法需要在运行时根据条件动态选择,或需要独立于客户端进行扩展和维护”的问题。在传统设计中,通常使用条件语句(如 if-else
或 switch-case
)根据配置或参数选择不同的算法分支。这导致:
- 代码臃肿:所有算法逻辑集中在单一方法或类中。
- 难以扩展:新增算法需要修改现有代码,违反开闭原则。
- 难以复用:算法逻辑无法独立复用。
- 紧耦合:上下文类与具体算法实现紧密耦合。
策略模式的核心思想是:将每个算法封装成一个独立的类(策略类),这些类实现一个共同的策略接口。上下文类(Context)持有对策略接口的引用,通过多态调用算法,而无需知道具体实现。算法的切换通过注入不同的策略对象实现。
该模式包含以下核心角色:
- Context(上下文):定义客户端使用的接口,包含一个对
Strategy
接口的引用。它将算法相关的请求委托给策略对象执行,而不关心具体实现。 - Strategy(策略接口):定义所有具体策略类共有的操作接口(如
execute()
、calculate()
)。它声明了算法的抽象行为。 - ConcreteStrategyA, ConcreteStrategyB, …(具体策略类):实现
Strategy
接口,封装具体的算法或行为实现。每个具体策略提供一种算法的完整实现。
策略模式的关键优势:
- 符合开闭原则:新增算法只需添加新的具体策略类,无需修改上下文或客户端代码。
- 符合单一职责原则:每个策略类只负责一种算法的实现。
- 算法可独立复用:策略类可被多个上下文或系统复用。
- 支持运行时切换:上下文可在运行时动态更换策略对象。
- 消除条件语句:将
switch
逻辑转化为对象组合。 - 易于单元测试:每个策略可独立测试。
与“状态模式”相比,策略模式关注算法的替换,状态模式关注状态驱动的行为变化;策略通常由客户端或配置决定,状态转换由内部逻辑驱动。与“命令模式”相比,命令封装请求,策略封装算法。与“模板方法模式”相比,模板方法在编译时通过继承固定算法骨架,策略在运行时通过组合动态选择算法。
策略模式适用于:
- 多种算法实现同一功能(如排序、压缩、加密、支付方式、运费计算)。
- 需要运行时动态选择算法。
- 需要避免庞大的条件分支。
- 需要独立测试或复用算法。
- 实现业务规则引擎或配置化行为。
二、策略模式的UML表示
以下是策略模式的标准 UML 类图:
#mermaid-svg-rSr2xo5BioFUgURw {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-rSr2xo5BioFUgURw .error-icon{fill:#552222;}#mermaid-svg-rSr2xo5BioFUgURw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rSr2xo5BioFUgURw .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-rSr2xo5BioFUgURw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rSr2xo5BioFUgURw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rSr2xo5BioFUgURw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rSr2xo5BioFUgURw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rSr2xo5BioFUgURw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rSr2xo5BioFUgURw .marker.cross{stroke:#333333;}#mermaid-svg-rSr2xo5BioFUgURw svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rSr2xo5BioFUgURw g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-rSr2xo5BioFUgURw g.classGroup text .title{font-weight:bolder;}#mermaid-svg-rSr2xo5BioFUgURw .nodeLabel,#mermaid-svg-rSr2xo5BioFUgURw .edgeLabel{color:#131300;}#mermaid-svg-rSr2xo5BioFUgURw .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-rSr2xo5BioFUgURw .label text{fill:#131300;}#mermaid-svg-rSr2xo5BioFUgURw .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-rSr2xo5BioFUgURw .classTitle{font-weight:bolder;}#mermaid-svg-rSr2xo5BioFUgURw .node rect,#mermaid-svg-rSr2xo5BioFUgURw .node circle,#mermaid-svg-rSr2xo5BioFUgURw .node ellipse,#mermaid-svg-rSr2xo5BioFUgURw .node polygon,#mermaid-svg-rSr2xo5BioFUgURw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rSr2xo5BioFUgURw .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-rSr2xo5BioFUgURw g.clickable{cursor:pointer;}#mermaid-svg-rSr2xo5BioFUgURw g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-rSr2xo5BioFUgURw g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-rSr2xo5BioFUgURw .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-rSr2xo5BioFUgURw .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-rSr2xo5BioFUgURw .dashed-line{stroke-dasharray:3;}#mermaid-svg-rSr2xo5BioFUgURw #compositionStart,#mermaid-svg-rSr2xo5BioFUgURw .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #compositionEnd,#mermaid-svg-rSr2xo5BioFUgURw .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #dependencyStart,#mermaid-svg-rSr2xo5BioFUgURw .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #dependencyStart,#mermaid-svg-rSr2xo5BioFUgURw .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #extensionStart,#mermaid-svg-rSr2xo5BioFUgURw .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #extensionEnd,#mermaid-svg-rSr2xo5BioFUgURw .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #aggregationStart,#mermaid-svg-rSr2xo5BioFUgURw .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw #aggregationEnd,#mermaid-svg-rSr2xo5BioFUgURw .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-rSr2xo5BioFUgURw .edgeTerminals{font-size:11px;}#mermaid-svg-rSr2xo5BioFUgURw :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}usesimplementsimplementsimplementsContext-strategy: Strategy+setStrategy(strategy: Strategy)+executeStrategy(data: Object)«interface»Strategy+execute(data: Object)ConcreteStrategyA+execute(data: Object)ConcreteStrategyB+execute(data: Object)ConcreteStrategyC+execute(data: Object)
图解说明:
Context
持有对Strategy
接口的引用。Context
通过setStrategy()
接受不同的策略实现。Context
的executeStrategy()
方法将调用委托给当前Strategy
的execute()
。ConcreteStrategy
实现execute()
,提供具体算法。- 客户端通过向
Context
注入不同的ConcreteStrategy
来改变其行为。
三、一个简单的Java程序实例及其UML图
以下是一个电商系统中运费计算的示例,支持多种运费计算策略(标准、加急、免费)。
Java 程序实例
// 策略接口:运费计算器interface ShippingCostStrategy { double calculate(double weight, double distance);}// 具体策略:标准运费class StandardShippingStrategy implements ShippingCostStrategy { private static final double RATE_PER_KG = 2.5; private static final double RATE_PER_KM = 0.1; @Override public double calculate(double weight, double distance) { double cost = (weight * RATE_PER_KG) + (distance * RATE_PER_KM); System.out.println(\"📦 标准运费: \" + weight + \"kg × $\" + RATE_PER_KG + \"/kg + \" + distance + \"km × $\" + RATE_PER_KM + \"/km = $\" + cost); return cost; }}// 具体策略:加急运费class ExpressShippingStrategy implements ShippingCostStrategy { private static final double BASE_FEE = 15.0; private static final double PREMIUM_RATE = 0.5; // per km @Override public double calculate(double weight, double distance) { double cost = BASE_FEE + (distance * PREMIUM_RATE); // 重量影响较小,主要按距离和基础费 System.out.println(\"⚡ 加急运费: 基础费 $\" + BASE_FEE + \" + \" + distance + \"km × $\" + PREMIUM_RATE + \"/km = $\" + cost); return cost; }}// 具体策略:免费运费(促销活动)class FreeShippingStrategy implements ShippingCostStrategy { @Override public double calculate(double weight, double distance) { System.out.println(\"🎁 免费运费: 订单满足促销条件,运费为 $0.00\"); return 0.0; }}// 上下文:购物车class ShoppingCart { private double totalAmount; private double weight; private double distance; private ShippingCostStrategy shippingStrategy; // 持有策略引用 public ShoppingCart(double totalAmount, double weight, double distance) { this.totalAmount = totalAmount; this.weight = weight; this.distance = distance; // 默认策略:标准运费 this.shippingStrategy = new StandardShippingStrategy(); System.out.println(\"🛒 购物车创建: 金额=$\" + totalAmount + \", 重量=\" + weight + \"kg, 距离=\" + distance + \"km\"); } // 动态设置运费策略 public void setShippingStrategy(ShippingCostStrategy strategy) { this.shippingStrategy = strategy; System.out.println(\"🔄 运费策略已切换\"); } // 计算总费用(商品金额 + 运费) public double calculateTotal() { double shippingCost = shippingStrategy.calculate(weight, distance); double total = totalAmount + shippingCost; System.out.println(\"💰 订单总费用: $\" + totalAmount + \" + $\" + shippingCost + \" = $\" + total); return total; } // 根据订单金额自动应用免费运费(演示策略选择逻辑) public void applyPromotion() { if (totalAmount >= 100.0) { System.out.println(\"🎉 订单金额 ≥ $100,应用免费运费促销!\"); setShippingStrategy(new FreeShippingStrategy()); } else { System.out.println(\"🛒 订单金额 < $100,不满足免费运费条件。\"); } }}// 客户端使用示例public class StrategyPatternDemo { public static void main(String[] args) { System.out.println(\"🛒 电商运费计算系统 - 策略模式示例\\n\"); // 场景1: 普通订单,使用标准运费 System.out.println(\"--- 场景1: 普通订单 ($80) ---\"); ShoppingCart cart1 = new ShoppingCart(80.0, 5.0, 200.0); cart1.calculateTotal(); // 使用默认标准策略 System.out.println(\"\\n--- 切换为加急运费 ---\"); cart1.setShippingStrategy(new ExpressShippingStrategy()); cart1.calculateTotal(); // 场景2: 大额订单,应用促销 System.out.println(\"\\n\\n--- 场景2: 大额订单 ($120) ---\"); ShoppingCart cart2 = new ShoppingCart(120.0, 3.0, 150.0); cart2.applyPromotion(); // 自动应用免费运费 cart2.calculateTotal(); System.out.println(\"\\n--- 手动切换回标准运费(演示灵活性)---\"); cart2.setShippingStrategy(new StandardShippingStrategy()); cart2.calculateTotal(); }}
实例对应的UML图(简化版)
#mermaid-svg-BkqS3pfjtabW7IPI {font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-BkqS3pfjtabW7IPI .error-icon{fill:#552222;}#mermaid-svg-BkqS3pfjtabW7IPI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BkqS3pfjtabW7IPI .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-BkqS3pfjtabW7IPI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BkqS3pfjtabW7IPI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BkqS3pfjtabW7IPI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BkqS3pfjtabW7IPI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BkqS3pfjtabW7IPI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BkqS3pfjtabW7IPI .marker.cross{stroke:#333333;}#mermaid-svg-BkqS3pfjtabW7IPI svg{font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BkqS3pfjtabW7IPI g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:\"trebuchet ms\",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-BkqS3pfjtabW7IPI g.classGroup text .title{font-weight:bolder;}#mermaid-svg-BkqS3pfjtabW7IPI .nodeLabel,#mermaid-svg-BkqS3pfjtabW7IPI .edgeLabel{color:#131300;}#mermaid-svg-BkqS3pfjtabW7IPI .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-BkqS3pfjtabW7IPI .label text{fill:#131300;}#mermaid-svg-BkqS3pfjtabW7IPI .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-BkqS3pfjtabW7IPI .classTitle{font-weight:bolder;}#mermaid-svg-BkqS3pfjtabW7IPI .node rect,#mermaid-svg-BkqS3pfjtabW7IPI .node circle,#mermaid-svg-BkqS3pfjtabW7IPI .node ellipse,#mermaid-svg-BkqS3pfjtabW7IPI .node polygon,#mermaid-svg-BkqS3pfjtabW7IPI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-BkqS3pfjtabW7IPI .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-BkqS3pfjtabW7IPI g.clickable{cursor:pointer;}#mermaid-svg-BkqS3pfjtabW7IPI g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-BkqS3pfjtabW7IPI g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-BkqS3pfjtabW7IPI .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-BkqS3pfjtabW7IPI .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-BkqS3pfjtabW7IPI .dashed-line{stroke-dasharray:3;}#mermaid-svg-BkqS3pfjtabW7IPI #compositionStart,#mermaid-svg-BkqS3pfjtabW7IPI .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #compositionEnd,#mermaid-svg-BkqS3pfjtabW7IPI .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #dependencyStart,#mermaid-svg-BkqS3pfjtabW7IPI .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #dependencyStart,#mermaid-svg-BkqS3pfjtabW7IPI .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #extensionStart,#mermaid-svg-BkqS3pfjtabW7IPI .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #extensionEnd,#mermaid-svg-BkqS3pfjtabW7IPI .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #aggregationStart,#mermaid-svg-BkqS3pfjtabW7IPI .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI #aggregationEnd,#mermaid-svg-BkqS3pfjtabW7IPI .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-BkqS3pfjtabW7IPI .edgeTerminals{font-size:11px;}#mermaid-svg-BkqS3pfjtabW7IPI :root{--mermaid-font-family:\"trebuchet ms\",verdana,arial,sans-serif;}usesimplementsimplementsimplementsShoppingCart-totalAmount: double-weight: double-distance: double-shippingStrategy: ShippingCostStrategy+setShippingStrategy(strategy: ShippingCostStrategy)+calculateTotal()+applyPromotion()«interface»ShippingCostStrategy+calculate(weight: double, distance: double)StandardShippingStrategy+calculate(weight: double, distance: double)ExpressShippingStrategy+calculate(weight: double, distance: double)FreeShippingStrategy+calculate(weight: double, distance: double)
运行说明:
ShoppingCart
是上下文,持有ShippingCostStrategy
引用。ShippingCostStrategy
定义运费计算接口。- 三种具体策略实现不同的计算逻辑。
ShoppingCart
通过setShippingStrategy()
动态更换策略。calculateTotal()
委托给当前策略的calculate()
方法。applyPromotion()
演示了根据业务规则自动选择策略。
四、总结
策略模式使用建议:
- 策略类可设计为无状态(推荐),便于共享和线程安全。
- 可结合“工厂模式”或“依赖注入”创建和注入策略。
- 在 Java 中,
enum
可实现简单策略(每个常量实现接口)。 - 可结合“组合模式”实现复合策略。
架构师洞见:
策略模式是“关注点分离”与“依赖倒置”原则的典范。在现代架构中,其思想已演变为微服务架构(每个服务是业务策略的实现)、插件系统(如 IDE 插件、浏览器扩展)、A/B 测试框架(不同策略对应不同用户组)、机器学习模型服务(不同模型作为预测策略)和 云原生配置管理(不同环境使用不同策略)。例如,Spring 框架的PasswordEncoder
、LoadBalancer
都是策略模式的应用;Kubernetes 的调度器可选择不同调度策略;在 AI 推理服务中,不同模型版本作为策略被动态切换。未来趋势是:策略模式将与Serverless 架构深度融合,每个函数即一个策略;在边缘计算中,设备根据网络状况选择数据处理策略;在量子计算中,不同量子算法作为策略被选择;在元宇宙中,用户交互逻辑可配置为不同策略。
掌握策略模式,是设计灵活、可配置、可演进系统的必备技能。作为架构师,应在设计任何需要“多选项”、“可配置行为”或“算法替换”的模块时,优先考虑策略模式。它不仅是模式,更是系统适应性的灵魂——它让系统摆脱了硬编码的束缚,具备了在变化的环境中动态选择最优路径的能力,从而构建出能够持续进化、自我优化的智能软件生态系统。