系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 1 讲
考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现
难度等级:⭐⭐⭐☆☆(结构清晰,Context 与 Strategy 的组合关系是填空重点)
一、考纲定位与模式定义
1.1 考纲要求
策略模式在软考中属于 行为型模式 的基础内容。考察形式包括:
-
上午选择题:判断场景描述所属模式;识别策略模式与状态模式、模板方法模式的区别;判断 Context 与 Strategy 的关系(组合 vs 继承)
-
下午设计题 :补全 Context 类中
setStrategy()和execute()方法的代码;补全 ConcreteStrategy 中算法实现的代码;识别类图中 Context、Strategy、ConcreteStrategy 三个角色
1.2 模式定义
策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
核心意图 :当一个系统需要 多种算法/策略 来完成同一件事(如不同的促销方式、不同的支付方式、不同的排序算法),且这些算法需要 动态切换 时,如果将所有算法写在一个类里用 if-else 或 switch 判断,会导致类臃肿、难以扩展。策略模式将每种算法封装为独立的策略类,Context 通过组合持有策略对象,运行时动态替换,让算法的变化独立于使用算法的客户。
通俗理解:
你去旅游,从 A 地到 B 地有多种策略:坐飞机(快但贵)、坐高铁(中等)、自驾(自由但累)。"去旅游" 这件事是 Context,"交通方式" 是 Strategy。你不需要在出门前就把所有交通方式的细节都写在行程单里(if-else),而是到了车站根据情况 动态选择 一种策略(买票/租车)。如果未来新增了 "骑共享单车" 这种方式,只需新增一个策略类,不需要修改 行程单(Context)的代码。
二、UML 类图与角色划分
┌─────────────────────────────────────────────────────────┐
│ Client │
│ + main() │
│ ├── Context ctx = new Context(); │
│ ├── ctx.setStrategy(new ConcreteStrategyA()); │
│ └── ctx.execute(); // 实际执行的是策略 A 的算法 │
└───────────────────────┬─────────────────────────────────┘
│ uses
▼
┌─────────────────────────────────────────────────────────┐
│ Context │
│ ── 上下文:持有策略引用,调用策略的算法 ── │
│ - strategy: Strategy ◄── 组合关系 │
│ + setStrategy(Strategy s) { │
│ this.strategy = s; │
│ } │
│ + execute() { │
│ strategy.algorithm(); // 委托给策略执行 │
│ } │
└───────────────────────┬─────────────────────────────────┘
│ delegates to
▼
┌─────────────────────────────────────────────────────────┐
│ <<interface>> │
│ Strategy │
│ + algorithm() │
└───────────────────────┬─────────────────────────────────┘
┌────────────┴────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ ConcreteStrategyA │ │ ConcreteStrategyB │
│ + algorithm() { │ │ + algorithm() { │
│ // 算法 A 实现 │ │ // 算法 B 实现 │
│ } │ │ } │
└─────────────────────┘ └─────────────────────┘
| 角色 | 职责 | 软考填空关键词 |
|---|---|---|
| Context(上下文) | 持有一个 Strategy 的引用,定义一个接口让客户端可以设置/更换策略,将操作委托给策略对象执行 | class + private Strategy strategy + setStrategy() + execute() |
| Strategy(抽象策略) | 定义所有支持的算法的公共接口,让 Context 可以统一调用 | interface / abstract class + algorithm() |
| ConcreteStrategy(具体策略) | 实现 Strategy 接口,封装具体的算法或行为 | implements Strategy + 具体算法实现 |
| Client(客户端) | 创建 Context 和 ConcreteStrategy,通过 setStrategy() 将策略注入 Context,调用 execute() |
new Context() + context.setStrategy(new XxxStrategy()) |
类图识别要点 :软考类图中,策略模式的核心特征是 Context 类通过组合(聚合)持有一个 Strategy 引用 (带空心菱形的实线),且 Context 中有
setStrategy()方法用于动态更换策略。如果类图里有一个类持有另一个接口的引用,且有 setter 方法可以替换 → 策略模式。
三、场景一:电商促销策略(最经典的策略模式)
业务背景:电商系统需要支持多种促销策略:满减(满 100 减 20)、折扣(打 8 折)、直降(立减 15 元)。结算时根据用户选择的优惠券类型,动态应用不同的计价策略。要求新增促销方式时不需要修改结算核心代码。
说明:这是策略模式最经典的例子,也是软考下午题最爱考的 "多种算法动态切换" 场景。
3.1 代码实现
java
// Strategy:促销策略接口
public interface PromotionStrategy {
double calculatePrice(double originalPrice);
}
// ConcreteStrategy:满减策略
public class FullReductionStrategy implements PromotionStrategy {
private double threshold;
private double reduction;
public FullReductionStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
@Override
public double calculatePrice(double originalPrice) {
if (originalPrice >= threshold) {
return originalPrice - reduction;
}
return originalPrice;
}
}
// ConcreteStrategy:折扣策略
public class DiscountStrategy implements PromotionStrategy {
private double discountRate;
public DiscountStrategy(double discountRate) {
this.discountRate = discountRate;
}
@Override
public double calculatePrice(double originalPrice) {
return originalPrice * discountRate;
}
}
// ConcreteStrategy:直降策略
public class DirectReductionStrategy implements PromotionStrategy {
private double reduction;
public DirectReductionStrategy(double reduction) {
this.reduction = reduction;
}
@Override
public double calculatePrice(double originalPrice) {
double finalPrice = originalPrice - reduction;
return finalPrice > 0 ? finalPrice : 0;
}
}
// Context:结算上下文
public class CheckoutContext {
private PromotionStrategy strategy;
public void setStrategy(PromotionStrategy strategy) {
this.strategy = strategy;
}
public double executeCheckout(double originalPrice) {
if (strategy == null) {
throw new IllegalStateException("未设置促销策略");
}
double finalPrice = strategy.calculatePrice(originalPrice);
System.out.println("原价: ¥" + originalPrice + " -> 实付: ¥" + finalPrice);
return finalPrice;
}
}
// Client:客户端
public class Client {
public static void main(String[] args) {
double originalPrice = 200.0;
CheckoutContext checkout = new CheckoutContext();
checkout.setStrategy(new FullReductionStrategy(100, 20));
checkout.executeCheckout(originalPrice);
checkout.setStrategy(new DiscountStrategy(0.8));
checkout.executeCheckout(originalPrice);
checkout.setStrategy(new DirectReductionStrategy(50));
checkout.executeCheckout(originalPrice);
}
}
关键体会:
-
CheckoutContext不知道具体是哪种促销策略,它只面向PromotionStrategy接口编程。 -
新增一种促销方式(如 "第二件半价"),只需新增一个
implements PromotionStrategy的类,不需要修改CheckoutContext的代码。这符合 开闭原则。 -
如果没有策略模式,结算代码里会充满
if (type.equals("fullReduction")) { ... } else if (type.equals("discount")) { ... }的臃肿代码。
四、场景二:支付渠道策略(实际开发场景)
业务背景 :系统需要支持多种支付方式:支付宝、微信支付、银行卡支付。每种支付方式的流程不同(支付宝需要调用支付宝 SDK,微信需要调用微信 SDK,银行卡需要调用银联接口),但对外提供的接口一致(pay(amount) 和 refund(orderId))。客户端根据用户选择的支付方式动态切换策略。
说明:这是实际开发中策略模式的典型应用,也是软考下午题中 "多算法封装" 场景的代表。
4.1 代码实现
java
// Strategy:支付策略接口
public interface PaymentStrategy {
boolean pay(String orderId, double amount);
boolean refund(String orderId, double amount);
}
// ConcreteStrategy:支付宝支付
public class AlipayStrategy implements PaymentStrategy {
@Override
public boolean pay(String orderId, double amount) {
System.out.println("[支付宝] 订单 " + orderId + " 支付 ¥" + amount);
return true;
}
@Override
public boolean refund(String orderId, double amount) {
System.out.println("[支付宝] 订单 " + orderId + " 退款 ¥" + amount);
return true;
}
}
// ConcreteStrategy:微信支付
public class WechatPayStrategy implements PaymentStrategy {
@Override
public boolean pay(String orderId, double amount) {
System.out.println("[微信支付] 订单 " + orderId + " 支付 ¥" + amount);
return true;
}
@Override
public boolean refund(String orderId, double amount) {
System.out.println("[微信支付] 订单 " + orderId + " 退款 ¥" + amount);
return true;
}
}
// ConcreteStrategy:银行卡支付
public class BankCardStrategy implements PaymentStrategy {
@Override
public boolean pay(String orderId, double amount) {
System.out.println("[银行卡] 订单 " + orderId + " 支付 ¥" + amount + "(调用银联接口)");
return true;
}
@Override
public boolean refund(String orderId, double amount) {
System.out.println("[银行卡] 订单 " + orderId + " 退款 ¥" + amount + "(原路退回)");
return true;
}
}
// Context:支付上下文
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public boolean executePay(String orderId, double amount) {
if (strategy == null) {
throw new IllegalStateException("未选择支付方式");
}
System.out.println("开始支付...");
return strategy.pay(orderId, amount);
}
public boolean executeRefund(String orderId, double amount) {
if (strategy == null) {
throw new IllegalStateException("未选择支付方式");
}
System.out.println("开始退款...");
return strategy.refund(orderId, amount);
}
}
// Client
public class Client {
public static void main(String[] args) {
PaymentContext payment = new PaymentContext();
String orderId = "ORD20240711001";
double amount = 299.0;
payment.setStrategy(new AlipayStrategy());
payment.executePay(orderId, amount);
payment.setStrategy(new WechatPayStrategy());
payment.executePay(orderId, amount);
payment.setStrategy(new BankCardStrategy());
payment.executePay(orderId, amount);
}
}
五、场景三:排序算法策略(算法族封装)
业务背景:系统需要对一组数据进行排序,但排序算法需要根据数据规模和特性动态选择:数据量小用冒泡排序(简单),数据量大用快速排序(高效),数据基本有序用插入排序(最优)。使用策略模式封装不同的排序算法,Context 根据条件自动选择或允许客户端手动选择。
说明:这是策略模式在算法选择场景中的典型应用,也是软考上午题中 "算法替换" 场景的代表。
5.1 代码实现
java
import java.util.Arrays;
// Strategy:排序策略接口
public interface SortStrategy {
void sort(int[] data);
}
// ConcreteStrategy:冒泡排序
public class BubbleSortStrategy implements SortStrategy {
@Override
public void sort(int[] data) {
System.out.println("[冒泡排序] 开始排序,数据量: " + data.length);
for (int i = 0; i < data.length - 1; i++) {
for (int j = 0; j < data.length - 1 - i; j++) {
if (data[j] > data[j + 1]) {
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
System.out.println("[冒泡排序] 完成: " + Arrays.toString(data));
}
}
// ConcreteStrategy:快速排序
public class QuickSortStrategy implements SortStrategy {
@Override
public void sort(int[] data) {
System.out.println("[快速排序] 开始排序,数据量: " + data.length);
quickSort(data, 0, data.length - 1);
System.out.println("[快速排序] 完成: " + Arrays.toString(data));
}
private void quickSort(int[] data, int low, int high) {
if (low < high) {
int pivot = partition(data, low, high);
quickSort(data, low, pivot - 1);
quickSort(data, pivot + 1, high);
}
}
private int partition(int[] data, int low, int high) {
int pivot = data[high];
int i = low - 1;
for (int j = low; j < high; j++) {
if (data[j] <= pivot) {
i++;
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
int temp = data[i + 1];
data[i + 1] = data[high];
data[high] = temp;
return i + 1;
}
}
// Context:排序上下文(带自动选择逻辑)
public class SortContext {
private SortStrategy strategy;
public void setStrategy(SortStrategy strategy) {
this.strategy = strategy;
}
public void autoSelectStrategy(int[] data) {
if (data.length <= 50) {
System.out.println("[上下文] 数据量较小,自动选择冒泡排序");
this.strategy = new BubbleSortStrategy();
} else {
System.out.println("[上下文] 数据量较大,自动选择快速排序");
this.strategy = new QuickSortStrategy();
}
}
public void executeSort(int[] data) {
if (strategy == null) {
autoSelectStrategy(data);
}
strategy.sort(data);
}
}
// Client
public class Client {
public static void main(String[] args) {
SortContext sorter = new SortContext();
int[] smallData = {5, 3, 8, 1, 2};
sorter.executeSort(smallData);
System.out.println();
int[] largeData = new int[1000];
for (int i = 0; i < largeData.length; i++) {
largeData[i] = (int) (Math.random() * 10000);
}
sorter.executeSort(largeData);
System.out.println();
int[] data = {9, 7, 5, 3, 1};
sorter.setStrategy(new BubbleSortStrategy());
sorter.executeSort(data);
}
}
六、三种场景对比与演进思路
| 维度 | 场景一:电商促销 | 场景二:支付渠道 | 场景三:排序算法 |
|---|---|---|---|
| Context | CheckoutContext(结算上下文) |
PaymentContext(支付上下文) |
SortContext(排序上下文) |
| Strategy | PromotionStrategy(促销策略) |
PaymentStrategy(支付策略) |
SortStrategy(排序策略) |
| ConcreteStrategy | FullReduction、Discount、DirectReduction |
Alipay、WechatPay、BankCard |
BubbleSort、QuickSort |
| 策略切换方式 | 客户端手动 setStrategy() |
客户端手动 setStrategy() |
支持自动选择 + 手动设置 |
| 算法参数 | 策略构造器传入(threshold、rate) | 无参(内部调用 SDK) | 无参(操作传入的数据) |
| 返回值 | double(最终价格) |
boolean(成功/失败) |
void(直接修改数组) |
| 新增策略成本 | 新增类,不改 Context | 新增类,不改 Context | 新增类,不改 Context |
| 软考考法 | 下午大题(补全 calculatePrice) | 下午大题(补全 pay/refund) | 上午选择题(识别算法替换) |
七、策略模式 vs 状态模式 vs 模板方法模式:核心对比
| 对比项 | 策略模式 | 状态模式 | 模板方法模式 |
|---|---|---|---|
| 模式分类 | 行为型 | 行为型 | 行为型 |
| 核心思想 | 封装 算法族,让它们可以相互替换 | 封装 状态转换,状态改变时行为改变 | 定义 算法骨架,子类实现某些步骤 |
| 切换触发者 | 客户端 主动调用 setStrategy() 切换 |
状态内部 根据条件自动转换到下一个状态 | 父类 控制流程,子类被动参与 |
| 类关系 | Context 组合 Strategy(聚合关系) | Context 组合 State(聚合关系) | 抽象类 继承(子类 extends 父类) |
| 客户端感知 | 客户端 知道 有哪些策略,主动选择 | 客户端 不知道 状态转换细节 | 客户端 不知道 模板方法的存在 |
| 代码结构 | 一个 Context + N Strategy 实现类 | 一个 Context + N State 实现类 + 可能的状态机 | 一个抽象类 + N 子类 |
| 新增扩展 | 新增策略类,不改 Context | 新增状态类,可能需修改状态转换逻辑 | 新增子类,实现抽象方法 |
| 典型应用 | 促销策略、支付方式、排序算法 | 订单状态机(待支付→已支付→已发货)、游戏角色状态 | 数据导入模板、报表生成模板 |
| 软考判断 | 看到 "多种算法可替换"、"客户端选择" → 策略 | 看到 "状态转换"、"自动改变行为" → 状态 | 看到 "算法骨架"、"子类实现特定步骤" → 模板方法 |
记忆口诀:策略是 "客户端选算法"(主动切换),状态是 "状态自己变"(自动转换),模板方法是 "父类定骨架子类填坑"(继承实现)。
八、软考高频考点与易混淆辨析
8.1 高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Context(上下文)、Strategy(抽象策略)、ConcreteStrategy(具体策略) |
| 核心思想 | 封装算法族,让它们可以相互替换,算法的变化独立于使用算法的客户 |
| Context 与 Strategy 关系 | 组合/聚合(Context 持有 Strategy 引用,不是继承) |
| 动态切换 | 通过 setStrategy() 方法在运行时更换策略 |
| 设计原则 | 开闭原则:新增策略只需增加 ConcreteStrategy,无需修改 Context |
| 消除 if-else | 策略模式的核心价值之一:避免 Context 中出现大量条件判断语句 |
| 与状态模式区别 | 策略:客户端主动选择;状态:状态内部自动转换 |
| 与模板方法区别 | 策略:组合 + 算法整体替换;模板方法:继承 + 算法骨架固定、部分步骤替换 |
| 适用场景 | ① 多种算法/策略需要动态切换 ② 需要避免多重条件判断 ③ 算法需要独立于客户端变化 |
8.2 易混淆辨析:策略 vs 状态 vs 模板方法
| 对比项 | 策略模式 | 状态模式 | 模板方法模式 |
|---|---|---|---|
| UML 结构 | Context 组合 Strategy | Context 组合 State | 抽象类定义模板,子类继承 |
| 切换控制权 | 外部(客户端/setter) | 内部(状态对象内部转换) | 无切换(子类固定) |
| 客户端角色 | 客户端 选择 策略并注入 | 客户端 触发 事件,状态自动流转 | 客户端 调用 模板方法 |
| 类数量 | 1 Context + N Strategy | 1 Context + N State + 可能的状态机 | 1 抽象类 + N 子类 |
| 关系类型 | 组合/聚合 | 组合/聚合 | 继承 |
| 软考考法 | 代码填空(补全 setStrategy + execute) | 代码填空(补全状态转换逻辑) | 代码填空(补全子类实现特定步骤) |
九、真题风格模拟与代码填空
模拟题 1(上午选择题)
以下关于策略模式的叙述中,正确的是()。
A. 策略模式属于结构型模式,主要用于封装对象的创建逻辑
B. 在策略模式中,Context 通过继承 Strategy 来获得算法实现
C. 策略模式定义了一系列算法,把它们封装起来,并且使它们可以相互替换,让算法的变化独立于使用算法的客户
D. 策略模式与状态模式的核心区别在于:策略模式由状态内部自动转换,状态模式由客户端主动选择
答案:C
解析:
-
A 错误:策略模式属于 行为型 模式,不是结构型。它封装的是 算法/行为,不是对象的创建。
-
B 错误:Context 通过 组合/聚合(持有 Strategy 引用)来使用算法,不是继承。如果用继承,就无法在运行时动态切换策略。
-
C 正确:这是策略模式的核心定义(GoF 原书定义)。
-
D 错误:说反了。策略模式由 客户端主动选择 (
setStrategy()),状态模式由 状态内部自动转换(状态对象根据条件改变 Context 的状态)。
模拟题 2(下午代码填空 --- 补全 Context 的 setStrategy 和 execute)
某系统使用策略模式实现不同的数据验证策略。
ValidationStrategy接口提供validate(String data)方法。LengthValidationStrategy和RegexValidationStrategy是具体策略。ValidatorContext是上下文。请补全(1)~(4)。
java
// Strategy
interface ValidationStrategy {
boolean validate(String data);
}
// ConcreteStrategy:长度验证
class LengthValidationStrategy implements ValidationStrategy {
private int min;
private int max;
public LengthValidationStrategy(int min, int max) {
this.min = min; this.max = max;
}
public boolean validate(String data) {
return data != null && data.length() >= min && data.length() <= max;
}
}
// ConcreteStrategy:正则验证
class RegexValidationStrategy implements ValidationStrategy {
private String pattern;
public RegexValidationStrategy(String pattern) {
this.pattern = pattern;
}
public boolean validate(String data) {
return data != null && data.matches(pattern);
}
}
// Context
class ValidatorContext {
private (1)______ strategy;
public void (2)______(ValidationStrategy strategy) {
this.strategy = strategy;
}
public boolean (3)______(String data) {
if (strategy == null) {
throw new IllegalStateException("未设置验证策略");
}
return (4)______;
}
}
public class Client {
public static void main(String[] args) {
ValidatorContext validator = new ValidatorContext();
validator.setStrategy(new LengthValidationStrategy(6, 20));
System.out.println(validator.executeValidate("hello123"));
}
}
答案:
-
(1)
ValidationStrategy -
(2)
setStrategy -
(3)
executeValidate(或validate) -
(4)
strategy.validate(data)
阅卷要点:
(1) 必须是
ValidationStrategy(抽象策略接口类型),体现面向接口编程。不能写LengthValidationStrategy或RegexValidationStrategy等具体类。(2) 必须是
setStrategy,这是策略模式 Context 的标准方法名,用于动态注入/更换策略。(3) 可以是
executeValidate、validate、execute等,表示执行操作的方法名。软考中通常用executeXxx()或contextInterface()。(4) 必须是
strategy.validate(data),将操作委托给策略对象执行。这是策略模式的核心:Context 不直接实现算法,而是委托给 Strategy。
模拟题 3(下午代码填空 --- 补全 ConcreteStrategy 和 Context 调用)
某系统使用策略模式实现不同的运费计算策略。
ShippingStrategy接口提供calculateShipping(double weight, double distance)方法。StandardShippingStrategy和ExpressShippingStrategy是具体策略。请补全(1)~(2)。
java
// Strategy
interface ShippingStrategy {
double calculateShipping(double weight, double distance);
}
// ConcreteStrategy:标准快递
class StandardShippingStrategy implements ShippingStrategy {
public double calculateShipping(double weight, double distance) {
return 10 + weight * 2 + distance * 0.5;
}
}
// ConcreteStrategy:顺丰快递(需要补全)
class ExpressShippingStrategy (1)______ ShippingStrategy {
public double calculateShipping(double weight, double distance) {
return (2)______;
}
}
// Context
class OrderContext {
private ShippingStrategy strategy;
public void setStrategy(ShippingStrategy strategy) {
this.strategy = strategy;
}
public double calculateTotal(double weight, double distance) {
return strategy.calculateShipping(weight, distance);
}
}
public class Client {
public static void main(String[] args) {
OrderContext order = new OrderContext();
order.setStrategy(new ExpressShippingStrategy());
double shipping = order.calculateTotal(2.5, 100);
System.out.println("运费: ¥" + shipping);
}
}
答案:
-
(1)
implements -
(2)
15 + weight * 3 + distance * 1(或等价表达式)
阅卷要点:
(1) 必须是
implements,具体策略类实现策略接口。如果写extends会 零分(除非 Strategy 是抽象类,但这里是 interface)。(2) 必须是根据题干描述的算法表达式。题目说 "首重 15 元,续重每公斤 3 元,每公里 1 元",所以表达式为
15 + weight * 3 + distance * 1。如果写10 + weight * 2 + distance * 0.5(标准快递的算法)会 零分。
十、常见陷阱与注意事项
陷阱 1:误认为策略是结构型模式
策略模式属于 行为型 模式,因为它关注的是 对象之间的行为/算法交互 (Context 将行为委托给 Strategy),而不是类与类之间的结构关系。软考上午题如果问"以下属于结构型模式的是",选项里出现"策略" 不能选。
陷阱 2:Context 继承 Strategy 而不是组合
这是软考 最高频的失分点 。策略模式的核心是 组合/聚合:
-
❌ 错误:
class Context extends Strategy(继承,静态绑定,无法动态切换) -
✅ 正确:
class Context { private Strategy strategy; }(组合,运行时注入)
软考代码填空里,Context 必须持有 private Strategy xxx 字段,并通过 setStrategy() 方法注入。如果 Context 继承 ConcreteStrategy,那就失去了策略切换的意义。
陷阱 3:与状态模式混淆
这是软考 最难的辨析题 之一。两者的 UML 结构几乎完全相同:
-
都有 Context 类
-
Context 都组合一个接口(Strategy / State)
-
都有多个具体实现类
终极区分方法:看切换的触发者。
-
策略模式 :客户端 主动调用
setStrategy()选择算法。Context 通常不自己决定切换。 -
状态模式 :状态对象内部 根据业务条件自动调用
context.setState()转换到下一个状态。客户端通常只触发事件,不直接选择状态。
如果题干说 "客户端根据条件选择不同的算法" → 策略。如果题干说 "对象内部状态改变时自动改变行为" → 状态。
陷阱 4:与模板方法模式混淆
| 策略模式 | 模板方法模式 |
|---|---|
| 组合 关系(Context 持有 Strategy) | 继承 关系(子类 extends 抽象类) |
| 算法 整体 替换(换一个 Strategy 对象) | 算法 骨架 固定,只替换部分步骤 |
| 运行时 动态 切换 | 编译时 静态 绑定(子类固定) |
新增策略:新增类 + setStrategy() |
新增模板:新增子类 + 继承 |
如果类图里是组合关系 + 可以动态替换 → 策略。如果是继承关系 + 抽象类定义模板方法 → 模板方法。
陷阱 5:策略类直接暴露给客户端,绕过 Context
虽然策略模式允许客户端直接创建 ConcreteStrategy 对象,但规范的做法是客户端通过 Context 来使用策略(context.setStrategy(new XxxStrategy()); context.execute();)。如果客户端直接调用 strategy.algorithm(),那就失去了 Context 作为统一入口的意义。软考代码填空里,如果题目给出了 Context 类,客户端应该通过 Context 调用,而不是直接调用 Strategy。
陷阱 6:Context 内部用 if-else 选择策略
这是 违背策略模式初衷 的写法。策略模式的目的就是 消除 Context 中的 if-else:
- ❌ 错误:
java
public double execute(double price) {
if (strategy instanceof FullReductionStrategy) { ... }
else if (strategy instanceof DiscountStrategy) { ... }
}
- ✅ 正确:
java
java
public double execute(double price) {
return strategy.calculatePrice(price); // 直接委托,无需判断类型
}
软考阅卷时,如果 Context 的 execute() 方法里出现了 instanceof 或 if-else 判断策略类型,通常会 扣分,因为这违背了策略模式的设计意图。
陷阱 7:忘记在 Context 中定义 setStrategy 方法
策略模式的核心是 动态切换 ,setStrategy() 方法是实现动态切换的关键。如果 Context 中只有构造器注入策略(没有 setter),那就只能在创建时确定策略,无法在运行时切换。虽然这种 "静态策略" 也是策略模式的一种用法,但软考通常考察的是 动态切换 的场景,代码填空里必须包含 setStrategy() 方法。
十一、总结
| 要点 | 内容 |
|---|---|
| 定义 | 定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户 |
| 分类 | 行为型模式(GoF 23 正式成员) |
| 核心三角色 | Context(上下文)、Strategy(抽象策略)、ConcreteStrategy(具体策略) |
| 核心思想 | 封装算法族,通过组合让 Context 在运行时动态切换策略 |
| Context 与 Strategy 关系 | 组合/聚合 (private Strategy strategy + setStrategy()) |
| 动态切换 | 通过 setStrategy() 在运行时更换策略 |
| 设计原则 | 开闭原则:新增策略只需增加 ConcreteStrategy,无需修改 Context |
| 消除 if-else | 避免 Context 中出现大量条件判断,将算法选择推迟到客户端 |
| 与状态模式区别 | 策略:客户端主动选择;状态:状态内部自动转换 |
| 与模板方法区别 | 策略:组合 + 算法整体替换;模板方法:继承 + 算法骨架固定 |
| 软考重点 | 代码填空(补全 Context 的 setStrategy() + execute() 委托调用);与状态模式的辨析(最高频);与模板方法的辨析 |
| 答题技巧 | 看到 "多种算法可替换"、"动态切换"、"避免 if-else"、"客户端选择策略" → 策略;看到 Context 组合 Strategy + setStrategy() → 确认策略 |
系列预告 :下一篇将讲 状态模式 ------ 当对象的行为取决于它的状态,且状态转换需要自动触发时,如何将状态封装为独立的类,让状态自己管理转换逻辑。策略与状态的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。