什么是模板模式?
模板设计模式(Template Method Pattern)是一种行为型设计模式,其核心定义为:定义一个算法的骨架流程(模板方法),将算法中某些步骤的具体实现延迟到子类中,使得子类可以在不改变算法整体结构的前提下,重新定义该算法的某些特定步骤。 简单来说,它通过抽象类定义了"做事情的步骤框架",其中包含:
- 一个或多个模板方法 (通常用
final
修饰,防止子类修改流程),用于规定步骤的执行顺序; - 一些基本方法(可以是抽象方法、具体方法或钩子方法),其中抽象方法由子类实现具体逻辑,具体方法提供公共实现,钩子方法(可选)允许子类选择性地调整流程。
- 本质是"固定骨架,变化细节",既保证了算法结构的一致性,又允许子类灵活定制特定步骤的实现。
适用场景
- 流程标准化,但细节差异化
当多个业务逻辑拥有相同的执行步骤框架,但部分步骤的具体实现因业务类型不同而有差异时。 例如:
- 框架中的生命周期管理(如Servlet的
init()
→service()
→destroy()
流程,具体业务由子类实现service()
); - 文档生成工具(固定步骤:"收集数据→格式化内容→输出文档",不同文档类型(PDF/Word)的"格式化"步骤不同);
- 测试框架(固定流程:"前置准备(setup)→执行测试(runTest)→清理资源(teardown)",具体测试逻辑由子类实现)。
- 需要控制子类的算法结构
当希望严格约束子类的执行流程,避免子类随意修改核心步骤顺序时。 例如:支付流程(固定步骤:"验证参数→调用支付接口→记录日志→返回结果",子类只能修改"调用支付接口"的具体实现,不能改变步骤顺序)。
- 代码复用需求高
当多个子类存在大量重复代码(如公共步骤的实现),希望将重复逻辑抽取到父类中统一维护时。 例如:各类饮料制作("煮沸水""倒入杯子"是公共步骤,抽取到父类;"冲泡""加调料"由子类实现)。
举个例子
通过一个制作饮料的例子来展示模板设计模式的实现。 在这个例子中,我们将创建一个饮料制作的模板,定义制作饮料的基本步骤(煮沸水、冲泡、倒入杯子、添加调料),然后让具体的饮料(咖啡和茶)实现各自特有的步骤。 示例展示了模板设计模式的核心要素:
- 抽象类(Beverage):
- 定义了模板方法(prepareRecipe()),该方法是final的,确保算法结构不被改变 - 包含抽象方法(brew()、addCondiments()),由子类实现
- 包含具体方法(boilWater()、pourInCup()),提供公共实现
- 包含钩子方法(customerWantsCondiments()),子类可以选择重写以改变算法行为
- 代码如下:
java
// 抽象类定义模板方法和基本步骤
public abstract class Beverage {
// 模板方法,定义了制作饮料的算法骨架
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
// 抽象方法,由子类实现具体的冲泡方式
protected abstract void brew();
// 抽象方法,由子类实现具体的添加调料方式
protected abstract void addCondiments();
// 具体方法,所有饮料制作都需要煮沸水
private void boilWater() {
System.out.println("煮沸水");
}
// 具体方法,所有饮料都需要倒入杯子
private void pourInCup() {
System.out.println("倒入杯子中");
}
// 钩子方法,子类可以重写来决定是否添加调料
protected boolean customerWantsCondiments() {
return true;
}
}
- 具体子类(Coffee、Tea):
- 实现抽象类中的抽象方法,提供特定于子类的实现
- 可以选择重写钩子方法,改变模板方法的默认行为
- 代码如下:
java
// 咖啡类,实现模板中的抽象方法
public class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
@Override
protected void addCondiments() {
System.out.println("添加糖和牛奶");
}
// 重写钩子方法,提供不同的行为
@Override
protected boolean customerWantsCondiments() {
// 实际应用中这里可能会询问用户
return true;
}
}
// 茶类,实现模板中的抽象方法
public class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("用沸水浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("添加柠檬");
}
// 重写钩子方法,提供不同的行为
@Override
protected boolean customerWantsCondiments() {
// 实际应用中这里可能会询问用户
return false;
}
}
- 客户端(Main):
- 通过抽象类接口使用具体子类,不需要知道具体实现细节
- 代码如下:
java
// 测试类,演示模板模式的使用
public class Main {
public static void main(String[] args) {
System.out.println("制作咖啡:");
Beverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("\n制作茶:");
Beverage tea = new Tea();
tea.prepareRecipe();
}
}
运行Main类会输出:
text
制作咖啡:
煮沸水
用沸水冲泡咖啡
倒入杯子中
添加糖和牛奶
制作茶:
煮沸水
用沸水浸泡茶叶
倒入杯子中
可以看到,茶没有添加调料,因为它的钩子方法返回了false,这展示了钩子方法如何改变算法的流程。 参考上面这个示例,分析下优缺点。
优点
- 封装不变,扩展可变
父类定义固定的算法骨架(不变部分),子类仅需实现差异化步骤(可变部分),符合"开闭原则"(对扩展开放,对修改关闭)。
- 提高代码复用性
公共步骤在父类中实现,避免子类重复编写,减少冗余代码,便于维护(修改公共逻辑只需改父类)。
- 保证算法一致性
子类无法修改父类中模板方法的步骤顺序,确保所有子类遵循统一的算法结构,避免流程混乱。
- 简化子类实现
子类只需关注自身特有的步骤细节,无需关心整体流程,降低开发难度。
缺点
- 类数量增加
每一个具体实现都需要定义一个子类,若业务场景复杂,可能导致类数量激增,增加系统复杂度。
- 灵活性受限
算法骨架被父类固定,若需调整步骤顺序或新增步骤,必须修改父类,可能影响所有子类(违反"开闭原则"的修改限制)。
- 子类依赖父类
子类的实现与父类的模板方法紧密耦合,若父类修改抽象方法(如新增抽象步骤),所有子类都必须同步修改,维护成本较高。
- 可能导致过度设计
若业务流程简单且变化少,使用模板模式可能引入不必要的抽象层次,增加理解难度。
总结
模板设计模式适合流程固定、细节多变的场景,能有效提升代码复用性和一致性,但需权衡其带来的类数量增加和灵活性限制。在使用时,应确保流程确实具备稳定性,避免为简单场景过度设计。