1.模板方法
代码示例
#include <iostream>
using namespace std;// 1. 抽象基类:定义算法骨架
class ZooShow {
public:// 构造函数初始化 expired 状态ZooShow(bool isExpired = false) : expired(isExpired) {}virtual ~ZooShow() {}// 【模板方法】定义骨架// 逻辑完全照搬图片:先看Show0结果,决定是否PlayGame,然后按序执行void Show() {if (Show0()) {PlayGame();}Show1();Show2();Show3();}private:bool expired; // 模拟图片中的私有变量// 私有方法:子类不可见,不可修改,只能由父类流程调用void PlayGame() {cout << " >> [中场福利] 既然没有超时,那就来玩个游戏吧!(after Show0, then play game)" << endl;}protected:// 【钩子方法】(Hook)// 图片中的逻辑:打印 show0,并判断是否过期virtual bool Show0() {cout << " [系统检查] 正在检查流程是否超时(Show0)..." << endl;if (!expired) {return true; // 没过期,返回 true,允许玩游戏}return false; // 过期了,返回 false,跳过游戏}// 【原语操作】具体的表演细节,留给子类实现virtual void Show1() = 0;virtual void Show2() = 0;virtual void Show3() = 0;
};// -----------------------------------------------------------// 2. 具体子类 A:海豚表演 (状态:未超时)
class DolphinShow : public ZooShow {
public:DolphinShow() : ZooShow(false) {} // 传入 false,代表没超时protected:// 海豚表演直接使用父类的 Show0 逻辑,不需要重写钩子void Show1() override { cout << " (海豚) 表演项目一:水上芭蕾" << endl; }void Show2() override { cout << " (海豚) 表演项目二:算术计算" << endl; }void Show3() override { cout << " (海豚) 表演项目三:高空顶球" << endl; }
};// 3. 具体子类 B:狮子表演 (状态:已超时)
class LionShow : public ZooShow {
public:LionShow() : ZooShow(true) {} // 传入 true,代表超时了protected:// 狮子表演也使用父类的 Show0 逻辑// 因为 expired 为 true,父类 Show0 会返回 false,从而跳过 PlayGamevoid Show1() override { cout << " (狮子) 表演项目一:钻火圈" << endl; }void Show2() override { cout << " (狮子) 表演项目二:吼叫" << endl; }void Show3() override { cout << " (狮子) 表演项目三:过独木桥" << endl; }
};// 4. 具体子类 C:特殊表演 (强行重写钩子)
class MagicShow : public ZooShow {
protected:// 魔术表演很特殊,不管是否超时,我都强制想玩游戏// 这里演示了子类“挂钩”并修改父类默认判断逻辑的能力bool Show0() override {cout << " [魔术特权] 无视时间限制,强制开启游戏!" << endl;return true; }void Show1() override { cout << " (魔术) 大变活人" << endl; }void Show2() override { cout << " (魔术) 逃脱术" << endl; }void Show3() override { cout << " (魔术) 消失" << endl; }
};// -----------------------------------------------------------int main() {cout << "=== 第一场:海豚表演 (正常时间) ===" << endl;ZooShow* show1 = new DolphinShow();show1->Show();delete show1;cout << endl;cout << "=== 第二场:狮子表演 (已超时) ===" << endl;ZooShow* show2 = new LionShow();show2->Show(); // 注意观察:这里不会打印 "PlayGame" 的内容delete show2;cout << endl;cout << "=== 第三场:魔术表演 (重写钩子) ===" << endl;ZooShow* show3 = new MagicShow();show3->Show();delete show3;return 0;
}
1. 定义
- 原文: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
- 解读:
- “算法的骨架”:指的是我们在父类
ZooShow中写的Show()函数。它规定了“先判断Show0,再PlayGame,再Show1...”这个死流程。 - “延迟到子类”:指的是具体的
Show1(),Show2()怎么做,父类不管,留给子类(海豚、鹦鹉)去写。
- “算法的骨架”:指的是我们在父类
- 核心价值: 子类可以在不改变算法结构(不修改
Show()函数里的流程)的情况下,重定义算法的某些特定步骤。
2. 解决的问题 (Stable vs. Changing)
这是设计模式的精髓——分离变与不变。
- 稳定点 (Stable):算法骨架
- 在
ZooShow的例子中,表演的流程顺序是绝对稳定的。不可能先谢幕(Show3)再开场。这个顺序逻辑必须在父类写死,防止子类乱改。
- 在
- 变化点 (Changing):子流程需要变化
- 具体的表演内容(钻火圈还是骑单车)是变化的。
3. 代码结构 (Code Structure)
图中标注的这三点是实现该模式的“铁律”:
- 基类中有骨架流程接口:
- 通常是一个 Public 非虚函数(如
void Show())。 - 注意: 为什么是非虚函数?因为我们不希望子类去覆写这个流程!这在 C++ 中有时被称为
NVI(Non-Virtual Interface) 手法的一种变体。
- 通常是一个 Public 非虚函数(如
- 所有子流程对子类开放并且是虚函数:
- 通常是 Protected Virtual 函数。
Protected:因为这些细节步骤不应该被客户端直接调用(观众不能直接命令海豚“钻火圈”,只能命令它“开始表演”)。Virtual:为了让子类能重写 (Override)。
- 多态使用方式:
- 客户端持有基类指针
ZooShow*,指向子类对象new DolphinShow。 - 调用
ptr->Show()时,执行的是父类的流程,但在流程内部调用的Show1()却是子类的实现。
- 客户端持有基类指针
4. 符合哪些设计原则?
A. 依赖倒置原则 —— 最核心的设计哲学
图中提到了两点:
- 子类扩展时,需要依赖基类的虚函数实现:
- 通常我们认为“谁调用谁,谁就依赖谁”。但在模板模式中,是父类(高层模块)调用子类(底层模块)的实现。
- 这就是著名的 “好莱坞原则” (Hollywood Principle):“别给我们打电话,我们会给你打电话。”
ZooShow(父类) 是导演,DolphinShow(子类) 是演员。导演控制流程,到点了喊演员上来演,而不是演员指挥导演。
- 使用者只依赖接口:
- 在
main函数中,我们使用的是ZooShow*指针。 - 使用者根本不需要知道具体是
Dolphin还是Lion,也不需要知道内部怎么PlayGame,只管调用Show()。
- 在
B. 封装变化点 (Encapsulate what varies) -> protected
- 图中关键词:
protected - 解读: 这是一个非常重要的 C++ 细节。
- 为什么不是
public? 如果Show1()是 public,外部用户就可以直接调用dolphin->Show1(),从而绕过了Show0的检查和PlayGame环节,打破了算法骨架的完整性。 - 为什么不是
private? 因为private子类看不见,没法重写。 - 所以必须是
protected: 这是父类和子类之间的“私密契约”,对外界(main函数)是隐藏的,但对家族内部是开放的。
- 为什么不是
C. 单一职责原则 (Single Responsibility Principle)
- 父类 (ZooShow): 只负责制定规则(算法骨架、流程控制)。
- 子类 (DolphinShow): 只负责执行细节(具体怎么表演)。
- 两者分工明确,互不干扰。
D. 最小知道原则 (Least Knowledge Principle / Law of Demeter)
- 解读: 外部调用者(Client)知道得越少越好。
- 代码体现:
- 调用者只知道有一个
Show()方法。 - 它不知道
Show0是什么,不知道PlayGame里面判断了expired,也不知道Show1具体是干嘛的。 - 系统的复杂度被封装在类内部,外部接口极其简单。
- 调用者只知道有一个
5. 如何扩展?
这是模板方法模式最大的威力所在:
- 实现子类继承基类:
class TigerShow : public ZooShow
- 复写子流程:
override Show1(),override Show2()...- 注意: 你完全不需要碰父类的
Show()函数,也不需要复制粘贴父类的逻辑。
- 通过多态调用方式使用:
ZooShow* ptr = new TigerShow();ptr->Show();