模板方法模式在父类中定义了一个算法的骨架,允许子类在不改变算法结构的前提下重写某些特定步骤。
核心目标:复用公共流程,差异化实现细节,确保算法步骤的稳定性和扩展性。
final
防止子类修改核心流程。典型应用:
doGet()
和 doPost()
方法。GameLoop
(初始化 → 更新 → 渲染 → 清理)。TestCase
生命周期(setup → test → teardown)。final
,定义不可变的算法步骤顺序。咖啡和茶的制作流程相同(烧水 → 冲泡 → 倒杯 → 加调料),但冲泡和加调料步骤不同。
public abstract class Beverage {
// 模板方法(final防止子类修改流程)
public final void prepareRecipe() {
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; // 默认加调料
}
}
// 咖啡
public class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("冲泡咖啡粉");
}
@Override
protected void addCondiments() {
System.out.println("加糖和牛奶");
}
// 覆盖钩子方法:用户可以选择不加调料
@Override
protected boolean customerWantsCondiments() {
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
System.out.print("是否加糖和牛奶?(y/n)");
// 模拟用户输入(实际中可读取控制台输入)
return "y";
}
}
// 茶
public class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}
@Override
protected void addCondiments() {
System.out.println("加柠檬");
}
}
public class Client {
public static void main(String[] args) {
Beverage coffee = new Coffee();
coffee.prepareRecipe();
/* 输出:
烧水
冲泡咖啡粉
倒入杯子
是否加糖和牛奶?(y/n)
加糖和牛奶
*/
Beverage tea = new Tea();
tea.prepareRecipe();
/* 输出:
烧水
浸泡茶叶
倒入杯子
加柠檬
*/
}
}
封装数据库操作的通用流程:连接 → 执行SQL → 关闭连接。
public abstract class DatabaseTemplate {
public final void execute(String sql) {
Connection conn = null;
try {
conn = getConnection(); // 抽象方法
executeStatement(conn, sql); // 具体方法
} catch (SQLException e) {
handleError(e); // 钩子方法
} finally {
closeConnection(conn); // 具体方法
}
}
// 抽象方法:由子类实现数据库连接
protected abstract Connection getConnection() throws SQLException;
// 具体方法:执行SQL(可被子类覆盖)
protected void executeStatement(Connection conn, String sql) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.execute(sql);
System.out.println("SQL执行成功");
}
}
// 钩子方法:默认错误处理(子类可覆盖)
protected void handleError(SQLException e) {
System.err.println("数据库错误:" + e.getMessage());
}
// 具体方法:关闭连接
private void closeConnection(Connection conn) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.err.println("关闭连接失败:" + e.getMessage());
}
}
}
}
public class MySQLDatabase extends DatabaseTemplate {
@Override
protected Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb";
return DriverManager.getConnection(url, "user", "password");
}
// 覆盖错误处理钩子
@Override
protected void handleError(SQLException e) {
System.err.println("MySQL错误代码:" + e.getErrorCode());
}
}
对比项 | 模板方法模式 | 策略模式 |
---|---|---|
实现方式 | 继承:子类覆盖父类方法 | 组合:通过接口注入策略对象 |
灵活性 | 结构固定,仅能扩展部分步骤 | 可完全替换算法 |
代码复用 | 父类集中通用逻辑 | 策略对象独立,无复用 |
适用场景 | 流程固定,步骤差异小 | 算法差异大,需动态替换 |
如何防止子类修改模板方法?
final
。钩子方法的典型应用场景?
模板方法模式是否违反开闭原则?
如何处理多个可变步骤?
实现一个 文件导出模板
,流程:打开文件 → 写入数据 → 关闭文件。要求:
设计一个 跨平台UI渲染模板
,流程:加载资源 → 布局 → 绘制。不同平台(Windows、macOS)实现资源加载和绘制细节。
在微服务架构中,如何利用模板方法模式统一服务调用流程(如鉴权 → 参数校验 → 执行逻辑 → 记录日志)?
框架级应用:
JdbcTemplate
:封装JDBC操作流程(获取连接、执行SQL、释放资源)。TestCase
:setUp()
→ testXxx()
→ tearDown()
。设计模式组合:
设计原则深化:
实际项目应用:
通过本教程,你可以掌握模板方法模式在算法复用和流程控制中的核心技巧,并能够灵活应用于需要统一框架但支持扩展的场景。若有疑问或需要代码调试,欢迎随时交流!