模板方法模式(Template Method Pattern),在 C++ 中常通过 非虚接口(NVI, Non-Virtual Interface) 惯用法来实现。
核心思想
-
基类定义一个非虚的公共成员函数(如 process()),它内部按照固定顺序调用一系列虚函数(通常是 protected)。
-
派生类重写这些虚函数,提供具体实现。
-
当通过派生类对象或基类指针/引用调用基类的非虚函数时,基类的代码被执行,但内部的虚函数调用会动态绑定到派生类的重写版本。
-
效果:基类控制算法骨架,派生类填充细节,实现了“派生类调用基类函数,基类函数再反向多态调用派生类函数”。
详细解释
派生类调用基类函数:例如 DerivedA a; a.process();,process() 是基类的非虚函数,所以直接调用 Base::process()。
基类函数多态调用派生类函数:Base::process() 内部调用 step1() 和 step2(),它们被声明为 virtual。由于 this 指向的是 DerivedA 对象,虚函数表(vtable)指向 DerivedA 的重写版本,因此实际执行 DerivedA::step1() 和 DerivedA::step2()。
好处:基类可以强制算法流程(如先 step1 再 step2),派生类无法改变流程,只能重写具体步骤。这符合“开闭原则”——对扩展开放(可新增派生类),对修改关闭(基类接口稳定)。
注意:在基类构造函数或析构函数中调用虚函数不会发生多态,因为此时派生类部分尚未初始化或已销毁,虚表指向基类。所以 NVI 通常用于普通成员函数
#include <iostream>
class Base {
public:
// 非虚公共接口,定义算法骨架
void process() {
std::cout << "Base::process() 开始\\n";
step1(); // 虚函数,多态调用
step2(); // 虚函数,多态调用
std::cout << "Base::process() 结束\\n";
}
virtual ~Base() = default;
protected:
// 虚函数,供派生类重写
virtual void step1() {
std::cout << " Base::step1() 默认实现\\n";
}
virtual void step2() {
std::cout << " Base::step2() 默认实现\\n";
}
};
class DerivedA : public Base {
protected:
void step1() override {
std::cout << " DerivedA::step1() 实现\\n";
}
void step2() override {
std::cout << " DerivedA::step2() 实现\\n";
}
};
class DerivedB : public Base {
protected:
void step1() override {
std::cout << " DerivedB::step1() 实现\\n";
}
// 不重写 step2,使用基类默认实现
};
int main() {
DerivedA a;
DerivedB b;
std::cout << "调用 a.process():\\n";
a.process(); // 派生类对象调用基类非虚函数
std::cout << "\\n调用 b.process():\\n";
b.process();
// 通过基类指针多态调用
Base* p = &a;
std::cout << "\\n通过基类指针调用 p->process():\\n";
p->process();
return 0;
}


