当前位置: 首页 > article >正文

模板方法模式详解:定义程序骨架与框架设计

目录

  • 1. 什么是模板方法模式
  • 2. 为什么需要模板方法模式
  • 3. 模板方法模式的结构
  • 4. 实现示例
  • 5. 钩子方法的使用
  • 6. 最佳实践与注意事项

1. 什么是模板方法模式

模板方法模式是一种行为型设计模式,它在一个方法中定义一个算法的骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

这种模式在框架设计中被广泛使用,比如:

  • Spring框架中的各种Template类
  • JUnit测试框架
  • Servlet的生命周期方法

2. 为什么需要模板方法模式

模板方法模式主要解决以下问题:

  1. 代码复用:将公共的算法骨架提取到父类中
  2. 扩展性:允许子类通过重写特定方法来改变算法的定步骤
  3. 控制反转:父类控制算法的整体流程,子类提供具体实现

3. 模板方法模式的结构

UML类图

AbstractClass
+templateMethod()
#primitiveOperation1()
#primitiveOperation2()
#hook()
ConcreteClass
#primitiveOperation1()
#primitiveOperation2()
#hook()

核心角色

  1. AbstractClass(抽象类)
    • 定义了一个模板方法,该方法包含算法的骨架
    • 定义了算法各个步骤的抽象方法
    • 可能包含钩子方法(hook methods)的默认实现
  2. ConcreteClass(具体类)
    • 实现父类中的抽象方法
    • 可以覆盖钩子方法
    • 不能覆盖模板方法

4. 实现示例

让我们通过一个制作饮料的例子来理解模板方法模式:

// 抽象类:定义饮料制作模板
public abstract class BeverageTemplate {
    
    // 模板方法,定义了制作饮料的算法骨架
    public final void prepareBeverage() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
    
    // 基本方法 - 具体方法
    private void boilWater() {
        System.out.println("将水煮沸");
    }
    
    // 基本方法 - 具体方法
    private void pourInCup() {
        System.out.println("倒入杯中");
    }
    
    // 基本方法 - 抽象方法,由子类实现
    protected abstract void brew();
    
    // 基本方法 - 抽象方法,由子类实现
    protected abstract void addCondiments();
    
    // 钩子方法,决定是否需要调料
    protected boolean customerWantsCondiments() {
        return true; // 默认返回true
    }
}

// 具体类:咖啡
public class Coffee extends BeverageTemplate {
    
    @Override
    protected void brew() {
        System.out.println("用沸水冲泡咖啡");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("加入糖和牛奶");
    }
}

// 具体类:茶
public class Tea extends BeverageTemplate {
    private boolean wantsLemon;
    
    public Tea(boolean wantsLemon) {
        this.wantsLemon = wantsLemon;
    }
    
    @Override
    protected void brew() {
        System.out.println("用沸水浸泡茶叶");
    }
    
    @Override
    protected void addCondiments() {
        System.out.println("加入柠檬");
    }
    
    @Override
    protected boolean customerWantsCondiments() {
        return wantsLemon;
    }
}

// 使用示例
public class BeverageDemo {
    public static void main(String[] args) {
        System.out.println("制作咖啡:");
        BeverageTemplate coffee = new Coffee();
        coffee.prepareBeverage();
        
        System.out.println("\n制作加柠檬的茶:");
        BeverageTemplate teaWithLemon = new Tea(true);
        teaWithLemon.prepareBeverage();
        
        System.out.println("\n制作不加柠檬的茶:");
        BeverageTemplate teaWithoutLemon = new Tea(false);
        teaWithoutLemon.prepareBeverage();
    }
}

运行结果:

制作咖啡:
将水煮沸
用沸水冲泡咖啡
倒入杯中
加入糖和牛奶

制作加柠檬的茶:
将水煮沸
用沸水浸泡茶叶
倒入杯中
加入柠檬

制作不加柠檬的茶:
将水煮沸
用沸水浸泡茶叶
倒入杯中

运行结果说明:

  1. 咖啡和茶都遵循相同的制作流程(模板方法定义的算法骨架)
  2. 它们各自实现了自己的冲泡(brew)和加调料(addCondiments)方法
  3. 茶类通过钩子方法(customerWantsCondiments)控制是否需要加入调料
  4. 整个流程由父类控制,子类只需要实现特定的步骤

5. 钩子方法的使用

钩子方法(Hook Methods)是模板方法模式中的一个重要概念,它让子类可以对父类的算法流程进行干预和补充。

5.1 钩子方法的类型和作用

  1. 条件型钩子方法
    • 返回布尔值,决定是否执行某个步骤
    • 用于控制算法流程
    • 例如前面例子中的 customerWantsCondiments()
  2. 空实现钩子方法
    • 提供默认的空实现
    • 子类可以选择性覆盖
    • 用于在算法中插入可选的处理步骤
  3. 默认实现钩子方法
    • 提供默认实现
    • 子类可以选择是否覆盖
    • 用于提供通用的处理逻辑

5.2 钩子方法示例

让我们通过一个文件处理的例子来详细说明钩子方法的使用:

// 抽象文件处理器
public abstract class FileProcessor {
    
    // 模板方法
    public final void processFile(String filePath) {
        if (!validateFile(filePath)) {  // 条件型钩子
            return;
        }
        
        readFile(filePath);
        
        beforeProcess();  // 空实现钩子
        
        processContent();
        
        if (needBackup()) {  // 条件型钩子
            backup();
        }
        
        afterProcess();  // 空实现钩子
        
        cleanup();  // 默认实现钩子
    }
    
    // 条件型钩子方法:验证文件
    protected boolean validateFile(String filePath) {
        // 默认实现:检查文件是否存在
        return new File(filePath).exists();
    }
    
    // 空实现钩子方法:处理前的准备工作
    protected void beforeProcess() {
        // 空实现,子类可以选择性覆盖
    }
    
    // 空实现钩子方法:处理后的收尾工作
    protected void afterProcess() {
        // 空实现,子类可以选择性覆盖
    }
    
    // 条件型钩子方法:是否需要备份
    protected boolean needBackup() {
        return false;  // 默认不备份
    }
    
    // 默认实现钩子方法:清理工作
    protected void cleanup() {
        System.out.println("执行基本清理工作");
        // 子类可以选择覆盖或通过super调用
    }
    
    // 抽象方法
    protected abstract void readFile(String filePath);
    protected abstract void processContent();
    protected abstract void backup();
}

// 文本文件处理器
public class TextFileProcessor extends FileProcessor {
    private String content;
    private boolean isImportant;
    
    public TextFileProcessor(boolean isImportant) {
        this.isImportant = isImportant;
    }
    
    @Override
    protected void readFile(String filePath) {
        System.out.println("读取文本文件: " + filePath);
        // 实际的文件读取代码...
    }
    
    @Override
    protected void processContent() {
        System.out.println("处理文本内容");
    }
    
    @Override
    protected void backup() {
        System.out.println("备份文本文件");
    }
    
    // 覆盖条件型钩子方法
    @Override
    protected boolean needBackup() {
        return isImportant;  // 根据文件重要性决定是否备份
    }
    
    // 覆盖空实现钩子方法
    @Override
    protected void beforeProcess() {
        System.out.println("文本处理前的准备工作");
    }
    
    // 扩展默认实现钩子方法
    @Override
    protected void cleanup() {
        super.cleanup();  // 调用父类的清理方法
        System.out.println("执行文本文件特定的清理工作");
    }
}

// 使用示例
public class FileProcessorDemo {
    public static void main(String[] args) {
        System.out.println("处理普通文本文件:");
        FileProcessor normalProcessor = new TextFileProcessor(false);
        normalProcessor.processFile("test.txt");
        
        System.out.println("\n处理重要文本文件:");
        FileProcessor importantProcessor = new TextFileProcessor(true);
        importantProcessor.processFile("important.txt");
    }
}

运行结果:

处理普通文本文件:
读取文本文件: test.txt
文本处理前的准备工作
处理文本内容
执行基本清理工作
执行文本文件特定的清理工作

处理重要文本文件:
读取文本文件: important.txt
文本处理前的准备工作
处理文本内容
备份文本文件
执行基本清理工作
执行文本文件特定的清理工作

5.3 钩子方法使用技巧

  1. 条件型钩子方法的使用
    • 用于控制算法流程的分支
    • 返回布尔值,命名应该表达明确的判断含义
    • 例如:shouldProcess()isValid()canExecute()
  2. 空实现钩子方法的使用
    • 在算法关键节点提供扩展点
    • 子类可以选择是否实现
    • 通常用于前置/后置处理
    • 例如:beforeXXX()afterXXX()onXXX()
  3. 默认实现钩子方法的使用
    • 提供基础实现,允许子类扩展
    • 子类可以选择完全覆盖或调用super
    • 适用于有通用处理逻辑的场景

5.4 注意事项

  1. 命名规范
    • 条件型钩子方法使用 is、should、can 等前缀
    • 空实现钩子方法使用 before、after、on 等前缀
    • 名称应该清晰地表达方法的用途
  2. 文档说明
    • 清晰说明钩子方法的作用
    • 说明默认行为
    • 说明子类可以如何使用该钩子
  3. 设计建议
    • 钩子方法应该是protected的
    • 不要在钩子方法中放置太多逻辑
    • 钩子方法应该是可选的,不影响核心算法

6. 最佳实践与注意事项

  1. 封装变化
    • 将容易变化的步骤定义为抽象方法
    • 将稳定的步骤实现在抽象类中
    • 使用钩子方法处理可选步骤
  2. 遵循开闭原则
    • 模板方法应该是final的,防止子类改变算法骨架
    • 通过添加新的子类来扩展功能
    • 不修改已有的代码结构
  3. 命名规范
    • 模板方法应该清晰地表达其用途
    • 抽象方法的命名应该反映其在算法中的作用
    • 钩子方法通常以 should、will、do 等词开头
  4. 注意事项
    • 避免模板方法过于复杂
    • 抽象方法的数量要适中
    • 在文档中清晰说明每个抽象方法的职责

使用场景

模板方法模式适用于以下场景:

  • 多个类有相似的算法,只是其中某些步骤不同
  • 需要控制子类扩展的时候
  • 一次性实现算法的不变部分,并将可变部分留给子类
  • 防止代码重复

优点

  1. 提高代码复用性
  2. 遵循开闭原则
  3. 符合单一职责原则
  4. 父类控制,子类实现

缺点

  1. 每个不同的实现都需要一个子类
  2. 可能会导致类的数量增加
  3. 父类可能会变得过于庞大

总结

模板方法模式是一种简单但强大的设计模式,它通过把不变的行为搬移到超类,去除子类中的重复复代码来体现它的优势。它是基于继承的代码复用的基本技术。在框架设计中有着广泛的应用。使用时要注意权衡父类和子类之间的责任分配,以及钩子方法的合理使用。


http://www.kler.cn/a/440948.html

相关文章:

  • Flutter_学习记录_Tab的简单Demo~真的很简单
  • 线段树 算法
  • python学opencv|读取图像(四十七)使用cv2.bitwise_not()函数实现图像按位取反运算
  • hive:基本数据类型,关于表和列语法
  • Kafka常见问题之 org.apache.kafka.common.errors.RecordTooLargeException
  • allegro修改封闭图形线宽
  • MongoDB-副本集
  • Java函数式编程【三】【Stream终止操作】【上】之【简单约简】
  • 跑步训练(蓝桥杯2020试题A)
  • 微知-python包管理工具pip如何查看安装了某个库?(pip3 show xxx;pip3 list; pip3 show xxx -v)
  • 自动驾驶---小米汽车智驾进展
  • React状态管理常见面试题目(一)
  • Spark执行计划解析后是如何触发执行的?
  • 表格树(有展开功能)数据量大导致渲染慢问题的解决方法
  • 【21天学习AI底层概念】day8 强人工智能会在什么时候实现?
  • [Unity Shader] 【图形渲染】Unity Shader的种类1-深入理解表面着色器(Surface Shader)
  • 设计模式12:状态模式
  • Leetcode经典题14--罗马数字和整数之间的相互转换
  • 【linux】shell(37)-脚本调试
  • Scala-异常
  • 网络安全、Web安全、渗透测试之笔经面经总结(二)
  • 鸿蒙项目云捐助第十讲鸿蒙App应用分类页面二级联动功能实现
  • QT TCP(socket)编程-服务器与客户端IP地址问题
  • 在 SQL Server 中获取指定字符所在有位置索引
  • stm32-- 存储-flash和ram
  • Hadoop概述