目录
- 1. 什么是模板方法模式
- 2. 为什么需要模板方法模式
- 3. 模板方法模式的结构
- 4. 实现示例
- 5. 钩子方法的使用
- 6. 最佳实践与注意事项
1. 什么是模板方法模式
模板方法模式是一种行为型设计模式,它在一个方法中定义一个算法的骨架,将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
这种模式在框架设计中被广泛使用,比如:
- Spring框架中的各种Template类
- JUnit测试框架
- Servlet的生命周期方法
2. 为什么需要模板方法模式
模板方法模式主要解决以下问题:
- 代码复用:将公共的算法骨架提取到父类中
- 扩展性:允许子类通过重写特定方法来改变算法的定步骤
- 控制反转:父类控制算法的整体流程,子类提供具体实现
3. 模板方法模式的结构
UML类图
核心角色
- AbstractClass(抽象类):
- 定义了一个模板方法,该方法包含算法的骨架
- 定义了算法各个步骤的抽象方法
- 可能包含钩子方法(hook methods)的默认实现
- 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 {@Overrideprotected void brew() {System.out.println("用沸水冲泡咖啡");}@Overrideprotected void addCondiments() {System.out.println("加入糖和牛奶");}
}// 具体类:茶
public class Tea extends BeverageTemplate {private boolean wantsLemon;public Tea(boolean wantsLemon) {this.wantsLemon = wantsLemon;}@Overrideprotected void brew() {System.out.println("用沸水浸泡茶叶");}@Overrideprotected void addCondiments() {System.out.println("加入柠檬");}@Overrideprotected 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();}
}
运行结果:
制作咖啡:
将水煮沸
用沸水冲泡咖啡
倒入杯中
加入糖和牛奶制作加柠檬的茶:
将水煮沸
用沸水浸泡茶叶
倒入杯中
加入柠檬制作不加柠檬的茶:
将水煮沸
用沸水浸泡茶叶
倒入杯中
运行结果说明:
- 咖啡和茶都遵循相同的制作流程(模板方法定义的算法骨架)
- 它们各自实现了自己的冲泡(brew)和加调料(addCondiments)方法
- 茶类通过钩子方法(customerWantsCondiments)控制是否需要加入调料
- 整个流程由父类控制,子类只需要实现特定的步骤
5. 钩子方法的使用
钩子方法(Hook Methods)是模板方法模式中的一个重要概念,它让子类可以对父类的算法流程进行干预和补充。
5.1 钩子方法的类型和作用
- 条件型钩子方法:
- 返回布尔值,决定是否执行某个步骤
- 用于控制算法流程
- 例如前面例子中的
customerWantsCondiments()
- 空实现钩子方法:
- 提供默认的空实现
- 子类可以选择性覆盖
- 用于在算法中插入可选的处理步骤
- 默认实现钩子方法:
- 提供默认实现
- 子类可以选择是否覆盖
- 用于提供通用的处理逻辑
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;}@Overrideprotected void readFile(String filePath) {System.out.println("读取文本文件: " + filePath);// 实际的文件读取代码...}@Overrideprotected void processContent() {System.out.println("处理文本内容");}@Overrideprotected void backup() {System.out.println("备份文本文件");}// 覆盖条件型钩子方法@Overrideprotected boolean needBackup() {return isImportant; // 根据文件重要性决定是否备份}// 覆盖空实现钩子方法@Overrideprotected void beforeProcess() {System.out.println("文本处理前的准备工作");}// 扩展默认实现钩子方法@Overrideprotected 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 钩子方法使用技巧
- 条件型钩子方法的使用:
- 用于控制算法流程的分支
- 返回布尔值,命名应该表达明确的判断含义
- 例如:
shouldProcess()
、isValid()
、canExecute()
- 空实现钩子方法的使用:
- 在算法关键节点提供扩展点
- 子类可以选择是否实现
- 通常用于前置/后置处理
- 例如:
beforeXXX()
、afterXXX()
、onXXX()
- 默认实现钩子方法的使用:
- 提供基础实现,允许子类扩展
- 子类可以选择完全覆盖或调用super
- 适用于有通用处理逻辑的场景
5.4 注意事项
- 命名规范:
- 条件型钩子方法使用 is、should、can 等前缀
- 空实现钩子方法使用 before、after、on 等前缀
- 名称应该清晰地表达方法的用途
- 文档说明:
- 清晰说明钩子方法的作用
- 说明默认行为
- 说明子类可以如何使用该钩子
- 设计建议:
- 钩子方法应该是protected的
- 不要在钩子方法中放置太多逻辑
- 钩子方法应该是可选的,不影响核心算法
6. 最佳实践与注意事项
- 封装变化:
- 将容易变化的步骤定义为抽象方法
- 将稳定的步骤实现在抽象类中
- 使用钩子方法处理可选步骤
- 遵循开闭原则:
- 模板方法应该是final的,防止子类改变算法骨架
- 通过添加新的子类来扩展功能
- 不修改已有的代码结构
- 命名规范:
- 模板方法应该清晰地表达其用途
- 抽象方法的命名应该反映其在算法中的作用
- 钩子方法通常以 should、will、do 等词开头
- 注意事项:
- 避免模板方法过于复杂
- 抽象方法的数量要适中
- 在文档中清晰说明每个抽象方法的职责
使用场景
模板方法模式适用于以下场景:
- 多个类有相似的算法,只是其中某些步骤不同
- 需要控制子类扩展的时候
- 一次性实现算法的不变部分,并将可变部分留给子类
- 防止代码重复
优点
- 提高代码复用性
- 遵循开闭原则
- 符合单一职责原则
- 父类控制,子类实现
缺点
- 每个不同的实现都需要一个子类
- 可能会导致类的数量增加
- 父类可能会变得过于庞大
总结
模板方法模式是一种简单但强大的设计模式,它通过把不变的行为搬移到超类,去除子类中的重复复代码来体现它的优势。它是基于继承的代码复用的基本技术。在框架设计中有着广泛的应用。使用时要注意权衡父类和子类之间的责任分配,以及钩子方法的合理使用。