饭馆吃饭: 点餐 -> 食用 -> 结账,因为这几个步骤是固定的 所以作为以样板
- 点餐可能有:粤菜 川菜 鲁菜等
- 结账可能有:现金 信用卡 微信
在固定步骤确定的情况下,通过多态机制在多个子类中对每个步骤的细节进行差异化实现,这就是模板方法模式能够达到的效果
一、一个具体实现范例的逐步重构
比如:
A公司有一个小游戏项目组 —> 开发 单机闯关打斗类游戏 (类似接机打拳类游戏)
一个游戏项目组最少需要三名担任不同角色的员工组成:游戏策划、游戏程序、游戏美术
- 策划:负责提出游戏的各种玩法,确定游戏中各种数值,例如人物生命值、魔法值
- 程序:需要与策划紧密配合通过代码来实现游戏策划奥球的各种游戏功能
- 美术:角色设计、道具设计、游戏特效等
游戏策划需求:
- 游戏主角:战士,攻击力不强,生命值比较多,抗揍,通过不断往前闯关,遇到敌人进行攻击;敌人在主角靠近时主动攻击主角
using namespace std; namespace _version1 { // 定义一个 战士 类 class Warrior { public: Warrior(int life, int magic, int attack) : m_life(life) , m_magic(magic) , m_attack(attack) { } // …一些其他成员函数 // 新增功能1 燃烧技能,敌人失去500生命值,主角自身失去300点生命值 void skill_burn() { cout << “让所以敌人失去500点生命值, 相关逻辑代码略…” << endl; cout << “主角自身失去300点生命值” << endl; m_life -= 300; cout << “播放技能‘燃烧’的技能特效” << endl; }
private: // 角色属性 int m_life; // 生命值 int m_magic; // 魔法值 int m_attack; // 攻击力 }; }; int main(int argc, char* argv[]) { // 版本一: 一个战士角色,后添加燃烧技能 _version1::Warrior role_obj(1000, 0, 200); role_obj.skill_burn(); }
- 新增功能2:新增主角:法师,攻击力很强,生命值比较少,不抗揍- 角色属性:生命值(初始值800,0时死亡游戏结束)、魔法值(初始值200)、攻击力(300)三个属性- 燃烧技能,敌人失去650生命值,主角自身失去100点生命值- 未来需求:- 新增角色牧师- 每个主角都有 技能:燃烧,但每个主角释放燃烧技能时效果不相同;不变的是:对敌人、主角都会产生影响- <br />> 新增功能2时需要考虑是否需要重新设计,重构代码,由此产生版本2```cppnamespace _version2 {// 战斗者父类class Fighter {public:Fighter(int life, int magic, int attack): m_life(life), m_magic(magic), m_attack(attack){}virtual ~Fighter() { } // 做父类时析构函数应该为虚函数// ...一些其他成员函数// 燃烧技能:对敌人产生影响, 有函数effect_enemy();对自身产生影响,有函数effect_self();展示技能函数play_effect()void skill_burn(){effect_enemy(); // 对敌人产生的影响effect_self(); // 对主角自身产生的影响play_effect(); // 播放“燃烧”的技能特效}protected:// 子类必须重新实现以下虚函数virtual void effect_enemy() = 0;virtual void effect_self() = 0;private:// 燃烧技能对所有角色效果一样,子类不需要重写void play_effect(){cout << "播放技能‘燃烧’的技能特效" << endl;}protected: // 可能被自雷访问,用protected修饰// 角色属性int m_life; // 生命值int m_magic; // 魔法值int m_attack; // 攻击力};// 战士类class Warrior : public Fighter {public:using Fighter::Fighter;// 对敌人产生的影响 损失500生命值void effect_enemy() override{cout << "2 战士:让所以敌人失去500点生命值, 相关逻辑代码略..." << endl;}// 对主角自身产生的影响 损失300生命值void effect_self() override{cout << "2 战士:主角自身失去300点生命值" << endl;m_life -= 300;}};// 法师类class Mage : public Fighter {public:using Fighter::Fighter;// 对敌人产生的影响 损失650生命值void effect_enemy() override{cout << "1 法师:让所以敌人失去650点生命值, 相关逻辑代码略..." << endl;}// 对主角自身产生的影响 损失100生命值void effect_self() override{cout << "1 法师:主角自身失去100点魔法值" << endl;m_magic -= 300;}};}int main(int argc, char* argv[]){// 版本二:// 战士主角,释放燃烧技能_version2::Warrior warrior(1000, 0, 200);warrior.skill_burn();cout << "---------------------------------------" << endl;// 法师主角,释放燃烧技能_version2::Mage mage(800, 0, 200);mage.skill_burn();}
战士和法师释放燃烧技能表现是不同的,这种不同的表现主要是通过Warrior和Mage继承父类的虚函数effect_enemy()、effect_self()实现的
二、引入模板方法模式
软件开发中需求变化频繁,开发人员要尝试寻找变化点,把变化部分和稳定部分分离开来,在变化的地方应用设计模式。
学习设计模式并不难,难的是在何时何地使用设计模式。
设计模式中往往会把 成员函数 说成是 算法
晚绑定,代码执行时才知道具体要执行哪个函数
早绑定,代码编译时就能确定执行的是哪个子类
模板方法模式的定义(实现意图):
- 定义了一个操作中的算法的骨架(稳定部分),而将一些步骤延迟到子类中实现(父类中定义虚函数,子类中实现/重写这个虚函数),从而达到在整体稳定的情况下能够产生一些变化的目的。
设计模式的经典总结:
- 设计模式的作用就是在变化和稳定中间寻找隔离点,分离稳定和变化,从而来管理变化
模板方法模式也被认为导致了以一种反向控制结构 — 这种结构被称为好莱坞法则 — 不要来调用我,我会调用你。
三、模板方法模式的UML图
UML:一种工具,通过该工具可以绘制一个类的结构图和类与类之间的关系。这种把所编写的代码以图形方式呈现对于代码的全局理解和掌握好处巨大
四、程序代码的进一步完善及应用联想
using namespace std; namespace _version1 { // 定义一个 战士 类 class Warrior { public: Warrior(int life, int magic, int attack) : m_life(life) , m_magic(magic) , m_attack(attack) { } // …一些其他成员函数 // 新增功能1 燃烧技能,敌人失去500生命值,主角自身失去300点生命值 void skill_burn() { cout << “让所以敌人失去500点生命值, 相关逻辑代码略…” << endl; cout << “主角自身失去300点生命值” << endl; m_life -= 300; cout << “播放技能‘燃烧’的技能特效” << endl; }
private: // 角色属性 int m_life; // 生命值 int m_magic; // 魔法值 int m_attack; // 攻击力 }; };
namespace _version2 { // 战斗者父类 class Fighter { public: Fighter(int life, int magic, int attack) : m_life(life) , m_magic(magic) , m_attack(attack) { } virtual ~Fighter() { } // 做父类时析构函数应该为虚函数
// ...一些其他成员函数// 燃烧技能:对敌人产生影响, 有函数effect_enemy();对自身产生影响,有函数effect_self();展示技能函数play_effect()void skill_burn(){if (can_use_skill() == false) { // 如果不能使用该技能,则直接返回return;}effect_enemy(); // 对敌人产生的影响effect_self(); // 对主角自身产生的影响play_effect(); // 播放“燃烧”的技能特效}
protected: // 子类必须重新实现以下虚函数 virtual void effect_enemy() = 0; virtual void effect_self() = 0; virtual bool can_use_skill() = 0;
private: // 燃烧技能对所有角色效果一样,子类不需要重写 void play_effect() { cout << “播放技能‘燃烧’的技能特效” << endl; }
protected: // 可能被自雷访问,用protected修饰 // 角色属性 int m_life; // 生命值 int m_magic; // 魔法值 int m_attack; // 攻击力 };
// 战士类 class Warrior : public Fighter { public: using Fighter::Fighter; // 对敌人产生的影响 损失500生命值 void effect_enemy() override { cout << “2 战士:让所以敌人失去500点生命值, 相关逻辑代码略…” << endl; }
// 对主角自身产生的影响 损失300生命值void effect_self() override{cout << "2 战士:主角自身失去300点生命值" << endl;m_life -= 300;}// 生命值不够300点,不能使用技能 燃烧bool can_use_skill() override{return m_life >= 300;}
};
// 法师类 class Mage : public Fighter { public: using Fighter::Fighter; // 对敌人产生的影响 损失650生命值 void effect_enemy() override { cout << “1 法师:让所以敌人失去650点生命值, 相关逻辑代码略…” << endl; }
// 对主角自身产生的影响 损失100生命值void effect_self() override{cout << "1 法师:主角自身失去100点魔法值" << endl;m_magic -= 300;}// 生命值不够300点,不能使用技能 燃烧bool can_use_skill() override{return m_magic >= 300;}
};
}
int main(int argc, char* argv[]) { // 版本一: 一个战士角色,后添加燃烧技能 _version1::Warrior role_obj(1000, 0, 200); role_obj.skill_burn();
// 版本二:// 战士主角,释放燃烧技能_version2::Warrior warrior(1000, 0, 200);warrior.skill_burn();cout << "---------------------------------------" << endl;// 法师主角,释放燃烧技能_version2::Mage mage(800, 0, 200);mage.skill_burn();
} ```
钩子方法:子类勾住父类从而反向控制父类行为的意思,因此起名为钩子方法
MFC框架(微软基础类库):通过MFC创建一个基于对话框的应用程序,自动调用OnInitDialog成员函数。
其它例子
- 车间能够装配很多零件,如果零件的装配工序非常固定,工序细节有微小变化,就可以针对零件创建一个父类,其中零件转配工序(成员函数)就非常适合采用模板方法模式来实现,而处理某道工序的细节可以直接放在子类(针对某个具体零件的类)虚函数中。
