TemplateMethod抽象类中,固定流程方法(如Run)须为public sealed,可变步骤按需选abstract(强制重写)或virtual(可选重写),钩子必须protected,避免new隐藏、构造函数调用及外部直接调用。

TemplateMethod抽象类里必须用virtual还是abstract?
关键看算法骨架中哪些步骤是强制子类实现、哪些允许默认行为。固定流程(比如Execute)必须是final语义——C#里用sealed修饰virtual方法,或直接写成非虚public方法;可变步骤则按需选virtual(提供默认实现)或abstract(强制重写)。
常见错误是把所有钩子都设为abstract,导致子类被迫实现空逻辑;或者全用virtual却不加protected访问修饰,外部误调用打乱流程。
-
abstract方法:用于核心差异点,如CalculateDiscount()、ValidateInput() -
virtual方法:用于可选扩展点,如OnBeforeProcess(),默认空实现 - 主入口方法(如
Run())必须是非虚public,内部调用virtual/abstract成员
为什么子类重写virtual方法后,父类Run()里调用的还是子类版本?
这是C#虚方法调用的动态绑定机制决定的——运行时根据实际对象类型找方法实现。只要子类没用new隐藏而是用override,父类骨架中的virtual调用就一定会走到子类重写体。
容易踩的坑是误用new关键字:它不参与多态,父类引用调用时仍执行父类方法,模板流程就失效了。
- 必须用
override,不能用new - 父类方法声明必须带
virtual或abstract - 子类方法签名(含返回值、参数)必须完全一致
如何避免子类绕过模板直接调用钩子方法?
模板方法模式的安全边界在于“只允许在骨架内触发钩子”。如果钩子是public,子类实例可能被外部直接调用,破坏流程顺序。正确做法是全部钩子设为protected,且主流程方法(如Execute())不暴露给子类以外代码。
另一个隐患是子类在构造函数里调用钩子——此时对象尚未初始化完成,可能引发NullReferenceException。
- 所有
virtual/abstract钩子方法必须是protected - 主入口方法(如
Start())应为public sealed,禁止子类重写 - 避免在构造函数中调用任何
virtual成员
异步场景下Template Method怎么写才安全?
C#里不能直接async virtual(编译报错),所以得拆开:骨架方法用async Task,钩子用virtual Task,子类override async Task。但要注意——父类await的是子类返回的Task,不是子类的async方法本身。
典型错误是子类重写方法忘了await,或父类用.Result阻塞,导致死锁(尤其在UI/ASP.NET同步上下文里)。
- 钩子方法签名应为
protected virtual Task DoWorkAsync() - 子类重写用
public override async Task DoWorkAsync() - 父类骨架中必须
await每个钩子调用,禁用.Wait()或.Result
OnAfterSave(),整个流程控制就崩了。模板方法真正起效的前提,是团队对protected边界的共识和代码审查习惯。


















