模板方法模式中的钩子方法是什么?
简化版
钩子方法是父类在模板流程中预留的可选扩展点,通常有默认实现或空实现,子类可以选择是否覆盖。它可以让子类影响流程中的某些细节,但不改变整体流程骨架。
详细版
模板方法模式中的步骤方法不一定都必须由子类实现。常见步骤有三类:
- 抽象方法:子类必须实现。
- 具体方法:父类已经实现,子类通常不改。
- 钩子方法:父类提供默认实现,子类按需覆盖。
钩子方法常见用途:
- 在流程前后扩展额外逻辑;
- 控制某个步骤是否执行;
- 允许子类提供默认参数;
- 给框架留扩展点。
例如:
protected boolean needNotify() {
return true;
}
模板方法中根据 needNotify() 决定是否发送通知。子类可以覆盖它返回 false,但不能改变整个流程顺序。
完整版教学
一、钩子方法为什么叫“钩子”
钩子方法就像父类在流程里预留的挂钩点。
父类说:“流程整体我来定,但这里我给你一个机会,你可以挂一点自己的逻辑。”
例如:
public final void execute() {
beforeExecute();
doExecute();
afterExecute();
}
protected void beforeExecute() {
}
protected void afterExecute() {
}
protected abstract void doExecute();
beforeExecute() 和 afterExecute() 就是典型钩子方法。子类不覆盖也能运行,覆盖后可以加自己的扩展逻辑。
二、钩子方法和抽象方法的区别
抽象方法是必须实现的变化点。
钩子方法是可选扩展点。
例如文件导入:
protected abstract void parseFile();
protected void afterImport() {
}
每种文件必须解析,所以 parseFile() 是抽象方法。导入后是否发通知不是所有子类都需要,所以 afterImport() 可以是钩子方法。
面试中可以这样说:抽象方法强制子类参与流程,钩子方法允许子类选择性参与流程。
三、钩子方法可以控制流程分支
钩子方法不一定只做前后扩展,也可以返回布尔值控制是否执行某步。
public final void importData() {
readFile();
parseFile();
saveData();
if (needNotify()) {
notifyResult();
}
}
protected boolean needNotify() {
return true;
}
子类可以覆盖:
protected boolean needNotify() {
return false;
}
这样父类流程仍然固定,但某个可选步骤可以被子类控制。
四、钩子方法不要滥用
钩子方法太多会让父类变得难懂。
如果一个模板流程里到处都是:
beforeA();
if (needB()) {
doB();
}
afterB();
beforeC();
说明这个父类可能承担了太多变化,模板方法已经不再清晰。
钩子方法应该用于少量、明确、稳定的扩展点,而不是把所有变化都塞进父类。
五、框架中大量使用钩子思想
很多框架都有钩子方法或类似机制。
例如:
- 生命周期回调;
- 初始化前后扩展;
- 拦截器前置和后置方法;
- 测试框架中的 setup/teardown;
- Web 请求处理中的前后置扩展点。
这些机制本质上都在表达:框架控制主流程,业务代码在指定点扩展。
六、钩子如何提供受控的可选扩展
导出流程默认不压缩,hook needCompress() 返回 false;ZIP 子类返回 true 后,只多执行 1 个压缩步骤而不改写整条流程。
template -> export -> if needCompress() -> compress -> finish
这里真正需要保护的是父类掌握的流程不变量:客户端只能进入模板入口,子类只能改写约定好的步骤。评审时应逐步标出哪些行为固定、哪些行为必须实现、哪些行为只是可选钩子,避免父类和子类互相猜测。
七、继承边界与流程契约验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 抽象步骤是“必须实现”,钩子是“可以覆写”;两者不能混为一谈。 |
| 适用边界 | 钩子适合可选行为和小分支,若十几个布尔钩子相互组合,流程已经难以推理,应拆策略或显式配置。 |
| 测试证据 | 记录步骤进入与退出序列,覆盖默认钩子、覆写钩子、步骤异常和清理路径 |
| 工程代价 | 重点评估父类改动会影响多少子类、protected 扩展面有多大,以及新增变化是否迫使无关子类一起修改 |
易错点:抽象步骤是“必须实现”,钩子是“可以覆写”;两者不能混为一谈。
八、常见误区与追问
- 误区:钩子方法必须是空方法。 它也可以返回默认布尔值或默认对象,核心是提供可选扩展和安全默认行为。
- 误区:模板方法要求所有步骤都由子类覆写。 稳定步骤应在父类直接实现;只有必要变化点才做抽象步骤,可选变化点才做有默认实现的钩子。
- 追问:布尔钩子和前后置钩子怎么选? 控制某步是否执行用布尔钩子,允许补充行为用 before/after 钩子,并保持名称和调用时机明确。
- 追问:模板方法为什么常声明为 final? 为了防止子类改写算法骨架和步骤顺序;若框架有意允许重写,则必须明确不变量和扩展契约。
- 追问:模板方法最关键的回归测试是什么? 是父类契约测试:所有子类都必须满足固定步骤顺序、公共前后置行为和异常清理规则。
九、加强记忆
钩子方法是模板方法模式里的“可选扩展点”。抽象方法要求子类必须实现,钩子方法允许子类按需覆盖;它能做前后扩展,也能控制可选步骤,但不能让整体流程失控。