← 返回题目列表

模板方法模式中的钩子方法是什么?

高频 中等 第 11 / 26 题 更新于 2026/07/28
模板方法模式钩子方法Hook扩展点

简化版

钩子方法是父类在模板流程中预留的可选扩展点,通常有默认实现或空实现,子类可以选择是否覆盖。它可以让子类影响流程中的某些细节,但不改变整体流程骨架。

详细版

模板方法模式中的步骤方法不一定都必须由子类实现。常见步骤有三类:

  1. 抽象方法:子类必须实现。
  2. 具体方法:父类已经实现,子类通常不改。
  3. 钩子方法:父类提供默认实现,子类按需覆盖。

钩子方法常见用途:

  • 在流程前后扩展额外逻辑;
  • 控制某个步骤是否执行;
  • 允许子类提供默认参数;
  • 给框架留扩展点。

例如:

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? 为了防止子类改写算法骨架和步骤顺序;若框架有意允许重写,则必须明确不变量和扩展契约。
  • 追问:模板方法最关键的回归测试是什么? 是父类契约测试:所有子类都必须满足固定步骤顺序、公共前后置行为和异常清理规则。

九、加强记忆

钩子方法是模板方法模式里的“可选扩展点”。抽象方法要求子类必须实现,钩子方法允许子类按需覆盖;它能做前后扩展,也能控制可选步骤,但不能让整体流程失控。