代码界的米其林大厨:模板方法模式的烹饪艺术
一、当代码开始「照菜谱下厨」
想象这样的后厨场景:
米其林主厨写下一份秘制菜谱,规定「煎牛排必须大火锁汁→低温慢烤→静置收汁」,但允许徒弟自由发挥酱料;
奶茶店规定「加冰→加茶→加料→封口」的标准流程,但各分店可自选茶底和小料...
这就是模板方法模式的精髓——像总厨的菜谱一样,固定烹饪框架,保留个性空间,让代码既能标准化生产,又不失灵活风味!
二、总厨的秘制菜谱(UML图)
┌─────────────┐
│AbstractClass│
├─────────────┤
│ +template() │
│ +step1() │
│ +step2() │
│ +step3() │
└──────△──────┘
│
┌─────────────┴─────────────┐
│ │
┌─────────────┐ ┌─────────────┐
│ Concrete │ │ Hook │
│ ClassA │ ├─────────────┤
└─────────────┘ │ +hook() │
└─────────────┘
- 总厨(AbstractClass):定义做菜流程模板
- 帮厨(ConcreteClass):实现具体步骤(比如切菜手法)
- 口味钩子(Hook):可选调整项(比如加不加香菜)
三、代码后厨的烹饪实况(场景实战)
1. 总厨的标准化菜谱(抽象类)
abstract class BeverageMaker {
// 模板方法(禁止子类修改final配方!)
public final void makeBeverage() {
boilWater();
brew();
pourInCup();
if (needCondiments()) {
addCondiments();
}
hook();
}
// 固定步骤:烧水
private void boilWater() {
System.out.println("🔥 烧开100℃山泉水");
}
// 固定步骤:倒入杯
private void pourInCup() {
System.out.println("🥤 倒入星巴克联名杯");
}
// 抽象方法:冲泡(由子类实现)
protected abstract void brew();
// 抽象方法:加料(由子类实现)
protected abstract void addCondiments();
// 钩子方法:默认加料(可选覆盖)
protected boolean needCondiments() {
return true;
}
// 钩子方法:空实现(可选扩展)
protected void hook() {}
}
2. 徒弟的个性实现(具体类)
// 徒弟A:手冲咖啡
class CoffeeMaker extends BeverageMaker {
protected void brew() {
System.out.println("☕ 注入现磨云南小粒咖啡");
}
protected void addCondiments() {
System.out.println("➕ 加奶加糖,拉个天鹅");
}
// 覆盖钩子:客人可选是否加糖
protected boolean needCondiments() {
Scanner sc = new Scanner(System.in);
System.out.print("要加糖吗?(y/n):");
return sc.nextLine().equalsIgnoreCase("y");
}
}
// 徒弟B:养生茶
class TeaMaker extends BeverageMaker {
protected void brew() {
System.out.println("🍵 放入宁夏枸杞+新疆红枣");
}
protected void addCondiments() {
System.out.println("🍯 加土蜂蜜和生姜片");
}
// 扩展钩子:泡完后保温
protected void hook() {
System.out.println("⏲️ 转入55℃恒温杯");
}
}
3. 米其林品鉴时刻
public class MichelinTest {
public static void main(String[] args) {
System.out.println("=== 咖啡师傅登场 ===");
BeverageMaker chef = new CoffeeMaker();
chef.makeBeverage(); // 按模板自动执行
System.out.println("\n=== 茶师傅登场 ===");
chef = new TeaMaker();
chef.makeBeverage();
}
}
四、模板方法 vs 流水线工人:总厨与代工厂的区别
| 维度 | 模板方法模式 | 工厂模式 |
|---|---|---|
| 控制权 | 父类掌控流程 | 子类决定对象创建 |
| 重点 | 步骤的标准化+定制化 | 对象的统一生产 |
| 灵活性 | 流程固定,步骤可变 | 完全自由创建对象 |
| 典型场景 | 算法框架固定 | 对象创建场景 |
| 现实类比 | 肯德基炸鸡标准流程 | 汽车零件组装厂 |
五、代码界的米其林餐厅
-
JdbcTemplate:
// Spring的经典实现 jdbcTemplate.execute((Connection conn) -> { // 固定流程:拿连接→执行→释放 return doBusiness(conn); // 业务代码插入点 }); -
Servlet生命周期:
init() → service() → destroy()的固定流程,doGet/doPost由子类实现 -
游戏关卡设计:
abstract class GameLevel { final void play() { loadScene(); playBGM(); startLevel(); // 抽象方法 calculateScore(); } } -
CI/CD流水线:
编译→测试→打包→部署的标准流程,不同项目实现具体测试步骤 -
银行交易系统:
验证→记录日志→执行交易→发送通知的标准流程
六、防炸厨房指南(最佳实践)
-
模板方法加final:
public final void template() { ... } // 禁止子类篡改流程 -
钩子方法要谨慎:
// 钩子方法命名要清晰 protected boolean customerWantsSugar() { ... } -
控制抽象粒度:
不要过度拆分步骤: ✅ 好的:brew() 冲泡 ❌ 坏的:boilWater() → wait3min() → addTeaLeaves() -
好莱坞原则:
"Don't call us, we'll call you."
—— 子类不要调用父类,等父类模板来调用 -
与策略模式结合:
// 在模板的某个步骤使用策略模式 void brew() { brewingStrategy.execute(); // 策略接口 }
七、米其林颁奖典礼
模板方法模式让代码成为标准与个性并存的料理大师:
- ✅ 要:用于流程固定但步骤多变的场景
- ✅ 要:用钩子方法提供灵活扩展点
- ❌ 不要:让子类破坏算法结构
- ❌ 不要:创建过于复杂的继承链
当你在Spring中优雅使用JdbcTemplate时,请想起模板方法模式——那个在后台默默守护标准化流程的隐形总厨!