【软考设计模式】策略模式:算法族的封装与动态切换精讲

系列定位:软考软件设计师 / 系统架构设计师 --- 行为型模式专题第 1 讲

考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现

难度等级:⭐⭐⭐☆☆(结构清晰,Context 与 Strategy 的组合关系是填空重点)


一、考纲定位与模式定义

1.1 考纲要求

策略模式在软考中属于 行为型模式 的基础内容。考察形式包括:

  • 上午选择题:判断场景描述所属模式;识别策略模式与状态模式、模板方法模式的区别;判断 Context 与 Strategy 的关系(组合 vs 继承)

  • 下午设计题 :补全 Context 类中 setStrategy()execute() 方法的代码;补全 ConcreteStrategy 中算法实现的代码;识别类图中 Context、Strategy、ConcreteStrategy 三个角色

1.2 模式定义

策略模式:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

核心意图 :当一个系统需要 多种算法/策略 来完成同一件事(如不同的促销方式、不同的支付方式、不同的排序算法),且这些算法需要 动态切换 时,如果将所有算法写在一个类里用 if-elseswitch 判断,会导致类臃肿、难以扩展。策略模式将每种算法封装为独立的策略类,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 FullReductionDiscountDirectReduction AlipayWechatPayBankCard BubbleSortQuickSort
策略切换方式 客户端手动 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) 方法。LengthValidationStrategyRegexValidationStrategy 是具体策略。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(抽象策略接口类型),体现面向接口编程。不能写 LengthValidationStrategyRegexValidationStrategy 等具体类。

  • (2) 必须是 setStrategy,这是策略模式 Context 的标准方法名,用于动态注入/更换策略。

  • (3) 可以是 executeValidatevalidateexecute 等,表示执行操作的方法名。软考中通常用 executeXxx()contextInterface()

  • (4) 必须是 strategy.validate(data),将操作委托给策略对象执行。这是策略模式的核心:Context 不直接实现算法,而是委托给 Strategy。


模拟题 3(下午代码填空 --- 补全 ConcreteStrategy 和 Context 调用)

某系统使用策略模式实现不同的运费计算策略。ShippingStrategy 接口提供 calculateShipping(double weight, double distance) 方法。StandardShippingStrategyExpressShippingStrategy 是具体策略。请补全(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() 方法里出现了 instanceofif-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() → 确认策略

系列预告 :下一篇将讲 状态模式 ------ 当对象的行为取决于它的状态,且状态转换需要自动触发时,如何将状态封装为独立的类,让状态自己管理转换逻辑。策略与状态的辨析是软考高频考点,咱们下回重点拆解。咱们下回见。

相关推荐
咖啡八杯8 小时前
GoF设计模式——迭代器模式
java·后端·设计模式·迭代器模式
ttod_qzstudio10 小时前
【软考设计模式】外观模式:复杂子系统的“门面“封装与代码填空精讲
设计模式·外观模式
geovindu17 小时前
CSharp: Prototype Pattern
开发语言·后端·设计模式·.net·原型模式·创建型模式
ttod_qzstudio17 小时前
【软考设计模式】组合模式:“部分-整体“层次结构与树形对象统一操作精讲
设计模式·组合模式
ttod_qzstudio19 小时前
【软考设计模式】装饰模式:“套娃“式增强与动态职责扩展精讲
设计模式·装饰模式
烬羽20 小时前
MCP 多服务器架构实战:一个 Agent 同时操控地图、浏览器和文件
设计模式·设计
阿pin1 天前
Android随笔-常用的设计模式
android·设计模式
儒雅的名1 天前
GoF设计模式——策略模式
设计模式·bash·策略模式
折哥的程序人生 · 物流技术专研1 天前
第4篇:Lambda 简化策略模式(Java 8+)
java·设计模式·策略模式·函数式编程·lambda·代码简化·扩充系列