策略模式与模板方法模式的对比

策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)是两种常见的行为型设计模式,它们都旨在提高代码的可复用性和可维护性,但在实现方式和适用场景上存在显著差异。


一、定义与核心思想

策略模式(Strategy Pattern)

策略模式定义了一系列算法,并将每个算法封装起来,使它们可以互换。该模式使得算法的变化独立于使用算法的客户。

  • 核心思想:将可变的部分(算法)从不可变的部分(上下文)中分离出来,通过组合的方式动态地选择算法。

  • 实现方式:通常使用接口或抽象类定义算法族,具体的算法实现这些接口或继承抽象类。上下文类持有一个策略对象的引用,并在需要时调用策略对象的方法。

模板方法模式(Template Method Pattern)

模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下重新定义算法的某些步骤。(百度智能云)

  • 核心思想:通过继承的方式,将算法的结构固定在父类中,而将可变的部分留给子类实现。

  • 实现方式:在抽象类中定义一个模板方法,模板方法按照一定的步骤调用其他方法,其中一些方法可以是抽象的,由子类实现。(维基百科)


二、比较分析

维度 策略模式 模板方法模式
设计目的 封装多个可互换的算法,使得算法可以独立于客户端变化 定义算法的骨架,将具体步骤延迟到子类实现
实现方式 通过组合,将策略对象注入到上下文中 通过继承,子类重写父类的抽象方法
行为变化时机 运行时可以动态切换策略 编译时确定,无法在运行时改变算法结构
控制反转 客户端决定使用哪种策略,控制权在客户端 父类控制算法结构,子类实现具体步骤,控制权在父类
适用场景 需要在运行时选择不同算法,且算法之间可以互换 多个子类有相同的操作顺序,但某些步骤实现不同

三、示例说明

策略模式示例

假设我们有一个文本格式化器,需要根据不同的格式化策略(如大写、小写、首字母大写)来格式化文本。(博客园, 百度智能云, 阿里云开发者社区)

// 策略接口
public interface TextFormatter {
    String format(String text);
}

// 具体策略:大写
public class UpperCaseFormatter implements TextFormatter {
    public String format(String text) {
        return text.toUpperCase();
    }
}

// 具体策略:小写
public class LowerCaseFormatter implements TextFormatter {
    public String format(String text) {
        return text.toLowerCase();
    }
}

// 上下文类
public class TextEditor {
    private TextFormatter formatter;

    public TextEditor(TextFormatter formatter) {
        this.formatter = formatter;
    }

    public void setFormatter(TextFormatter formatter) {
        this.formatter = formatter;
    }

    public void publishText(String text) {
        System.out.println(formatter.format(text));
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor(new UpperCaseFormatter());
        editor.publishText("Hello World"); // 输出:HELLO WORLD

        editor.setFormatter(new LowerCaseFormatter());
        editor.publishText("Hello World"); // 输出:hello world
    }
}

在上述示例中,TextEditor 类可以在运行时动态地切换不同的格式化策略。

模板方法模式示例

假设我们有一个数据处理流程,包括读取数据、处理数据和保存数据,不同的数据类型(如文本数据、图像数据)在处理步骤上有所不同。

// 抽象类
public abstract class DataProcessor {
    // 模板方法
    public final void process() {
        readData();
        processData();
        saveData();
    }

    protected void readData() {
        System.out.println("Reading data...");
    }

    protected abstract void processData();

    protected void saveData() {
        System.out.println("Saving data...");
    }
}

// 具体实现类:文本数据处理
public class TextDataProcessor extends DataProcessor {
    protected void processData() {
        System.out.println("Processing text data...");
    }
}

// 具体实现类:图像数据处理
public class ImageDataProcessor extends DataProcessor {
    protected void processData() {
        System.out.println("Processing image data...");
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        DataProcessor textProcessor = new TextDataProcessor();
        textProcessor.process();
        // 输出:
        // Reading data...
        // Processing text data...
        // Saving data...

        DataProcessor imageProcessor = new ImageDataProcessor();
        imageProcessor.process();
        // 输出:
        // Reading data...
        // Processing image data...
        // Saving data...
    }
}

在上述示例中,DataProcessor 类定义了数据处理的整体流程,而具体的数据处理逻辑由子类实现。


四、总结与建议

  • 策略模式 适用于需要在运行时根据不同情况选择不同算法的场景,强调算法的可互换性和独立性。(博客园)

  • 模板方法模式 适用于多个子类具有相同的操作顺序,但某些步骤实现不同的场景,强调算法结构的稳定性和步骤的可变性。(博客园)

在实际开发中,选择哪种模式应根据具体需求而定。如果需要在运行时灵活地切换算法,策略模式是更好的选择;如果算法的整体结构固定,只有部分步骤需要定制,模板方法模式更为适用。(博客园)

此外,这两种模式并非互斥,可以结合使用。例如,在模板方法模式中某个步骤的实现可以采用策略模式,以提高代码的灵活性和可维护性。



Java 设计模式:策略模式 (Strategy) 与模板方法模式 (Template Method) 的使用指南

模式定义与结构对比

策略模式通过组合方式封装可互换的算法。典型结构中,有一个抽象策略接口(Strategy)定义算法方法,多个具体策略类(ConcreteStrategyA/B/C)实现该接口,Context上下文类持有一个Strategy引用并调用其方法。例如:

// 策略接口
interface Strategy { 
    void execute(); 
}
// 具体策略类
class ConcreteStrategyA implements Strategy {
    public void execute() { System.out.println("Strategy A"); }
}
// 上下文类
class Context {
    private Strategy strategy;
    public Context(Strategy s) { this.strategy = s; }
    public void executeStrategy() { strategy.execute(); }
}

在客户端可以动态为Context设置不同策略。策略模式特点是控制权在客户端,通过组合关系选择算法;多套算法独立互换,适用于运行时切换算法的场景。

模板方法模式通过继承方式定义固定算法骨架。典型结构中,抽象父类(AbstractClass)实现一个templateMethod(),在该方法中按固定流程调用多个抽象方法或钩子(primitiveOperation1()primitiveOperation2()等),而具体子类(ConcreteClass)继承该父类并实现这些抽象步骤。例如:

abstract class Game {
    // 模板方法:定义流程骨架
    public final void play() {
        runGame(); choosePerson(); startGame(); endGame();
    }
    protected abstract void runGame();
    protected void choosePerson() { }
    protected abstract void startGame();
    protected abstract void endGame();
}
class ContraGame extends Game {
    protected void runGame()   { System.out.println("启动魂斗罗II..."); }
    protected void startGame() { System.out.println("开始魂斗罗游戏"); }
    protected void endGame()   { System.out.println("结束魂斗罗游戏"); }
}

模板方法模式将算法的固定流程封装在基类中,子类仅重写具体步骤,保证了整体结构稳定。模板方法模式控制权在抽象父类,依赖继承关系;适用于算法步骤固定但个别步骤可变的场景。

模式选择决策路径

  • 是否需要动态切换算法? 若系统需要在运行时根据条件选择不同算法或行为(如多种支付方式、多种排序/压缩算法等),且这些算法相对独立互换,应考虑使用策略模式。策略模式强调算法的可互换性和独立性,算法间一般没有重复代码,可随时注入新的策略。
  • 算法流程是否固定? 若项目中存在一个明确的流程骨架,但需要在各个步骤中插入不同的实现(如框架在执行过程的钩子步骤),则应考虑模板方法模式。模板方法模式适用于整体流程不变,但需要对子步骤进行抽象的场景。
  • 控制权在何处? 若希望由父类控制算法流程(调用顺序固定、子类只能定制细节),倾向模板方法;若希望由客户端或上下文决定使用哪种策略(算法由上下文组合而来),则为策略模式。
  • 扩展灵活性需求: 策略模式可在运行时自由切换策略,更灵活;模板方法模式通过继承扩展,灵活性相对较低,适合在编译时确定的场景。

综上,可简单归纳为:多套可替换算法用策略模式;单一算法模板,固定流程可变细节用模板方法。

模式典型应用场景

  • 策略模式典型场景: 多算法(或策略)可互换且没有共用实现的场景。例如不同支付方式(CreditCard、PayPal)、不同排序或搜索策略、图片/视频编解码算法、多种压缩/加密方式等。标准库中,Collections.sort() 通过传入不同的 Comparator(策略)实现不同排序算法。在Spring MVC中,不同URL对应的处理器(Handler)或安全认证策略通常采用策略模式。其他场景还有游戏AI中选择不同路径规划策略、计算器支持不同运算符、不同业务规则(如优惠策略)等。
  • 模板方法模式典型场景: 需要在框架或基类中定义固定执行流程,只允许子类定制部分步骤。例如Spring框架中的 JdbcTemplateRestTemplateRedisTemplate 等常用模板类,固定数据库连接、事务控制、资源关闭等流程,只让子类提供具体SQL或数据映射逻辑;又如游戏框架在启动游戏、结束游戏等流程不变、只让具体游戏类实现不同游戏内容。其他场景包括事务管理、文件处理管道、算法框架(模板方法模式常称为“钩子模式”)等。模板方法模式适合在多个子类间共享业务框架的场景,保证公共流程代码不重复。

易混淆的案例分析

  • 场景对比:多种实现 vs 固定流程。 例如某报表系统既可以导出为 PDF,也可以导出为 Excel。如果这两种导出方式是两个完全不同的实现逻辑(几乎没有共享代码),那么可以用策略模式,各自实现一个 ExportStrategy;但如果导出流程相同(如读取数据、写入文件),只是各自的写入细节不同,则可以用模板方法将公共流程写在抽象基类中,子类只重写文件格式处理。
  • 案例示例:订单处理流程。 假设订单处理分为“普通订单”和“促销订单”,两者流程大体相同,只是促销订单要多一步优惠计算。如果优惠步骤差异较大,则可以在 AbstractOrderHandler 中定义固定流程 handleOrder()(先验证、再付款、最后通知),并将优惠计算定义为一个抽象方法由子类实现(模板方法模式)。如果促销订单与普通订单的整个付款流程和优惠流程都截然不同,则更适合定义两个策略(Strategy)在上下文中替换。
  • 警惕控制反转方向: 模板方法强调父类掌控流程,子类重写“钩子”步骤;策略模式强调客户端选择策略,由上下文调用具体实现。如果设计时发现一个模式能满足条件但代码看起来需要反向(比如本来想用策略却固定了流程顺序),应重新评估使用是否正确。掌握两者区别后,可根据“算法复用 vs 算法可替换”这条主线做出正确选择。

模式的组合使用建议

在实际开发中,模板方法模式与策略模式可以结合使用以发挥各自优势。例如在的商品编码生成案例中,使用模板方法抽象出“获取当前编码→生成新编码→更新编码”这三个固定流程步骤,将共性流程放在抽象类中,再使用策略模式封装“如何生成新编码”的具体规则。这样既保证了整体流程的一致性,又使新增编码规则时只需添加新的策略实现。实践建议:使用模板方法定义稳定的算法框架,将变化点通过策略接口抽象出来;客户端在执行时先走模板流程,在需要变动的地方调用策略方法,以达到“骨架固定,细节可插拔”的效果。

总结: :当需求需要多个可互换算法时优先考虑策略模式;当需求流程固定且仅需子类定制部分步骤时优先考虑模板方法模式。理解其控制权和复用性的本质区别,并结合实际场景做出选择,能够帮助开发者在工程实践中正确应用这两种模式。

posted @ 2025-06-01 01:07  gongchengship  阅读(331)  评论(0)    收藏  举报