模板方法应在抽象类中声明为public且non-virtual(不加virtual/abstract),确保不可重写;子步骤按需设为protected abstract(强制实现)或protected virtual(可选覆盖);调用顺序必须严格固定于模板方法体内。

抽象类里怎么写模板方法的骨架
模板方法模式的核心是「把算法骨架定义在抽象类,具体步骤延迟到子类实现」。关键不是用 abstract 修饰每个步骤,而是用 virtual 或 abstract 控制可重写性,同时用 final(C# 中是 sealed)保护模板方法本身不被重写——但 C# 没有直接标记「方法不可重写」的语法糖,得靠设计约束。
常见错误是把整个流程写成 public virtual,结果子类绕过骨架直接调用步骤,破坏封装。正确做法是:
-
TemplateMethod()声明为public且non-virtual(即不加virtual),确保调用入口唯一 - 子步骤按需声明为
protected virtual(允许空实现或默认逻辑)或protected abstract(强制子类实现) - 避免在抽象类构造函数中调用
virtual方法——可能触发子类未初始化的重写逻辑,引发NullReferenceException
子类 override 时哪些地方最容易出错
子类继承后不是所有 virtual 方法都该重写,也不是重写了就安全。典型问题包括:
- 重写
protected virtual void Step2()时,忘了调用base.Step2(),而父类该方法里其实做了资源清理或日志记录 - 在
override方法里调用了抽象类尚未初始化的字段(比如抽象类构造器末尾才赋值的_config),但子类override方法已在基类构造中被间接触发 - 误把模板方法本身标为
virtual,导致子类重写后跳过原骨架,比如重写了Execute()却没调用base.Execute(),整个流程失控
建议:只对真正需要定制的步骤设为 virtual;若某步逻辑「可选但推荐保留默认行为」,用 virtual + 空实现;若「必须变化」,才用 abstract。
C# 里如何避免模板方法被意外绕过
和 Java 不同,C# 没有 final 方法关键字,所以无法语法级禁止子类重写模板方法。能做的只有设计防御:
- 把模板方法设为
public且不带virtual,子类无法重写(这是默认行为,但常被忽略) - 在抽象类内部用私有委托或本地函数封装核心流程,只暴露一个不可重写的 public 入口,例如:
public void Run() => _runLogic();
其中_runLogic是privateAction,在构造时绑定具体实现 - 配合 XML 文档注释明确标注「此方法为模板入口,禁止重写」,配合团队 Code Review 约束
注意:.NET 5+ 支持 sealed 修饰 override 方法,但不能用于首次声明——所以只能在中间继承层封住,无法根治最上层抽象类的模板方法。
抽象类 vs 接口 + 默认实现,模板方法该选哪个
从 C# 8 开始接口支持 default implementation,看起来能替代抽象类做模板方法。但实际不推荐:
- 接口不能包含字段、构造函数、
protected成员——而模板方法常依赖共享状态(如_stepCount)、初始化逻辑、受保护的钩子方法 - 接口默认方法是
public,无法设为protected,子类可能误调用步骤导致顺序错乱 - 一个类可实现多个接口,但模板方法要求「单继承骨架」,否则多个接口的默认
Run()会冲突
结论很直接:模板方法模式在 C# 中必须用抽象类。接口适合定义契约,抽象类才适合定义可复用的算法骨架。
真正容易被忽略的是:抽象类里的非抽象方法如果访问了子类尚未构造完成的字段,运行时不会报编译错误,但会在子类构造器执行前就触发 NRE——这种 bug 很难定位,务必在抽象类中把依赖推迟到 TemplateMethod() 内部首次使用时再初始化。










