使用模板方法模式有哪些常见误区?
简化版
模板方法模式常见误区包括:流程不稳定也强行抽父类、抽象步骤过多导致子类被迫空实现、钩子方法泛滥、父类变成上帝类、子类绕过模板方法直接调用步骤,以及把所有代码复用都理解成继承复用。
详细版
使用模板方法模式要避免这些问题:
- 流程本身经常变化,却强行固定在父类;
- 父类抽象过宽,子类实现大量无关方法;
- 钩子方法太多,流程变得难以预测;
- 父类承担太多业务细节,变成上帝类;
- 模板方法没有限制重写,子类随意改变流程;
- 步骤方法暴露过多,外部绕过模板流程调用;
- 继承层次过深,维护成本越来越高;
- 明明需要运行时组合,却选择了继承。
面试中可以强调:模板方法适合稳定流程,不适合复杂多变流程;设计时要控制抽象步骤数量,模板方法最好保持稳定和清晰。
完整版教学
一、误区一:流程不稳定还强行抽模板
模板方法模式的前提是流程骨架稳定。
如果不同子类流程差异很大,父类为了兼容它们就会不断增加判断和钩子:
if (needStepA()) {
stepA();
}
if (needStepB()) {
stepB();
}
最后模板方法会变得很难读。
这种情况下,可能应该用策略模式、责任链模式或显式流程编排,而不是继续往父类塞扩展点。
二、误区二:抽象步骤过多
父类如果定义太多抽象方法,子类就会被迫实现很多自己不关心的方法。
例如:
protected abstract void stepA();
protected abstract void stepB();
protected abstract void stepC();
protected abstract void stepD();
某些子类可能只需要 stepA,但仍要实现其他步骤,最后写一堆空方法。
这说明抽象层次可能有问题。可以把非必需步骤改成钩子方法,或者拆分不同模板父类。
三、误区三:钩子方法泛滥
钩子方法很方便,但太多会让流程不可预测。
读父类时,你会看到很多可选点;读子类时,又要到处找它覆盖了哪些钩子。
如果一个模板方法需要大量钩子才能工作,说明这个流程抽象不够稳定。
钩子方法应该少而关键,名字要清晰,默认行为要安全。
四、误区四:父类变成上帝类
模板父类应该负责公共流程,不应该吞下所有业务细节。
如果父类里既有订单逻辑、支付逻辑、库存逻辑,又有各种子类特判,它就不再是模板,而是难以维护的上帝类。
父类应该依赖抽象步骤,而不是识别具体子类。
如果父类出现大量:
if (this instanceof SomeSubclass)
基本说明设计已经坏了。
五、误区五:模板方法没有保护流程
如果模板方法可以被子类随意重写,那么流程就失去了约束。
很多场景下,模板方法应该声明为 final,步骤方法则用 protected 留给子类。
此外,步骤方法不要随便暴露给外部调用。外部应该调用模板方法,而不是直接调用某个内部步骤。
六、误区六:把继承当成唯一复用方式
模板方法是继承复用,但现代工程里通常更谨慎地使用继承。
如果变化维度很多,组合往往更灵活。
例如不同导入流程既有不同文件格式,又有不同校验规则、不同保存目标、不同通知方式。强行用继承会导致子类组合爆炸。
这时可以用模板方法固定大流程,再把某些变化步骤委托给策略对象。
七、用不变量审查抽象父类
审查一个含 8 个步骤、6 个钩子的父类时,先标出必须固定的步骤;若只有 2 步真正稳定,说明模板骨架抽得过早。
compare flows -> identify invariants -> minimal hooks -> contract tests
这里真正需要保护的是父类掌握的流程不变量:客户端只能进入模板入口,子类只能改写约定好的步骤。评审时应逐步标出哪些行为固定、哪些行为必须实现、哪些行为只是可选钩子,避免父类和子类互相猜测。
八、继承边界与流程契约验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 模板方法的质量取决于不变量是否真实稳定,而不是抽象方法数量是否足够多。 |
| 适用边界 | 父类不应读取所有子类的业务字段,也不应让构造过程调用可覆写方法,否则可能在子类初始化完成前动态分派。 |
| 测试证据 | 构造最小子类覆盖每个钩子组合,验证父类不变量、异常清理和禁止绕过的入口 |
| 工程代价 | 重点评估父类改动会影响多少子类、protected 扩展面有多大,以及新增变化是否迫使无关子类一起修改 |
易错点:模板方法的质量取决于不变量是否真实稳定,而不是抽象方法数量是否足够多。
九、常见误区与追问
- 误区:在父类构造器中调用钩子可以减少初始化代码。 可覆写方法可能访问尚未初始化的子类字段,产生难以定位的半初始化错误。
- 误区:模板方法要求所有步骤都由子类覆写。 稳定步骤应在父类直接实现;只有必要变化点才做抽象步骤,可选变化点才做有默认实现的钩子。
- 追问:如何验证模板没有被子类破坏? 除子类单测外,还要建立父类契约测试,校验步骤顺序、异常清理和所有实现共有的不变量。
- 追问:模板方法为什么常声明为 final? 为了防止子类改写算法骨架和步骤顺序;若框架有意允许重写,则必须明确不变量和扩展契约。
- 追问:模板方法最关键的回归测试是什么? 是父类契约测试:所有子类都必须满足固定步骤顺序、公共前后置行为和异常清理规则。
十、加强记忆
模板方法模式的误区可以记成“流程不稳别抽、抽象别太宽、钩子别太多、父类别太重、流程别被子类改乱”。它适合稳定骨架,不适合把所有变化都塞进继承体系。