← 返回题目列表

模板方法为什么通常要声明为 final?步骤方法访问权限怎么设计?

高频 中等 第 12 / 26 题 更新于 2026/08/02
模板方法模式final访问控制继承设计

简化版

模板方法通常声明为 final,目的是保护固定流程骨架,防止子类重写整个流程、跳过关键步骤或改变执行顺序。可变步骤一般设计成 protected abstractprotected 钩子方法,供子类在受控范围内扩展;公共入口保持稳定,内部步骤不随意暴露成 public。核心原则是:父类控制流程,子类只填变化点。

详细版

模板方法模式的价值在于“流程固定,步骤可变”。如果模板方法本身可以被子类随意重写,那么固定流程就失去保护:

abstract class ImportTemplate {
    public final void importFile(File file) {
        validate(file);
        List<Row> rows = parse(file);
        save(rows);
        afterImport(rows);
    }

    protected abstract List<Row> parse(File file);
    protected void afterImport(List<Row> rows) {}
}

这里 importFile 是稳定入口,声明为 finalparse 是必须由子类实现的变化步骤;afterImport 是可选钩子。

如果把所有方法都设成 public 或模板入口不加限制,子类和外部调用方都可能绕过父类流程,导致校验、事务、日志、资源释放等关键逻辑失效。

记忆钩子:模板方法的 final 不是炫技,而是在说“流程骨架归父类管,子类别改剧本”。

完整版教学

面试提示:访问控制题要讲清楚哪些方法允许扩展、哪些方法必须封死,这是模板方法保证流程不变形的关键。

一、模板方法模式的核心是保护流程骨架

模板方法模式把公共流程沉淀在父类中:

step1 校验
step2 解析
step3 保存
step4 后置处理

不同子类只改其中少数步骤。父类要保证所有子类都按同一个顺序执行,否则模板方法模式就退化成普通继承。

二、为什么模板方法常用 final

如果模板入口可重写:

class BadImporter extends ImportTemplate {
    public void importFile(File file) {
        save(parse(file)); // 跳过 validate
    }
}

子类可以跳过校验、审计、锁、事务、资源释放等关键步骤。final 的价值就是防止这种破坏。

public final void importFile(File file) {
    validate(file);
    List<Row> rows = parse(file);
    save(rows);
}

这让父类的流程契约稳定下来。

三、步骤方法通常用 protected

步骤方法给子类实现,不一定给外部调用方使用,所以常用 protected

protected abstract List<Row> parse(File file);
protected void afterImport(List<Row> rows) {}

这样外部只能调用模板入口 importFile,不能直接调用 parse 绕过流程。

四、抽象方法和钩子方法要区分

类型写法含义
必填步骤protected abstract子类必须实现
默认步骤protected 默认实现子类可覆盖
判断钩子protected boolean控制可选分支
固定步骤privatefinal子类不能改

不是所有步骤都要交给子类。越关键、越稳定的步骤,越应该留在父类。

五、不要把步骤方法都做成 public

如果步骤方法是 public:

importer.parse(file);
importer.save(rows);

外部调用方可能绕开模板流程,直接调内部步骤。这样父类提供的校验、异常处理、事务边界都可能失效。

模板方法模式一般只暴露一个或少数几个公共入口。

六、final 不是所有语言和框架都必须

有些语言没有 final,有些框架为了代理或测试不建议滥用 final。这时要通过约定、文档、测试或访问控制保护流程。

面试回答可以说:Java 中常见做法是模板方法声明为 final;如果受框架限制不能 final,也要明确“不允许子类重写模板入口”的契约。

七、访问权限体现扩展边界

一个稳的模板类大致是:

public abstract class AbstractJob {
    public final void execute() {
        before();
        doExecute();
        after();
    }

    private void before() {}
    protected abstract void doExecute();
    protected void after() {}
}

public 是对外入口,private 是父类内部固定逻辑,protected 是留给子类的变化点。

八、常见误区与追问

  • 误区:模板方法不加 final 也完全一样。 子类可重写入口后,父类就无法保证流程顺序和关键步骤。
  • 误区:步骤方法都应该 public,方便调用。 这会让外部绕开模板流程,破坏封装。
  • 误区:所有 protected 方法都应该让子类覆盖。 稳定步骤可以 private 或 final,避免扩展点泛滥。
  • 误区:用了 final 就一定是模板方法。 final 只是保护手段,核心仍是固定骨架和可变步骤。
  • 追问:Spring 代理遇到 final 方法怎么办? CGLIB 代理不能覆盖 final 方法,需结合具体框架机制选择设计。
  • 追问:步骤方法用 protected 还是 private? 子类需要扩展用 protected;父类固定逻辑用 private 或 final。
  • 追问:钩子方法是否应该 abstract? 可选扩展通常给默认实现;必须由子类提供的步骤才 abstract。

九、加强记忆

  1. 模板入口常用 final 保护流程骨架。
  2. 子类只实现变化步骤,不改整体剧本。
  3. 必填步骤用 protected abstract
  4. 可选扩展用 protected 钩子默认实现。
  5. 固定步骤用 private 或 final 收紧边界。
  6. 对外只暴露模板入口,避免绕过流程。