第二章 模板方法模式

第二章 模板方法模式

第一节 一个具体实现范例的逐步重构

(1)模板方法模式核心概念铺垫

(1.1) 模式本质:“固定步骤 + 差异化实现”

  • 生活类比案例:“到饭馆吃饭” 的固定流程
    • 固定步骤:点餐 → 食用 → 结账(所有食客均遵循此流程,即 “模板骨架”)
    • 差异化细节:
      • 点餐:有人点粤菜、有人点鲁菜
      • 结账:有人用现金、有人用微信 / 信用卡
  • 模式核心定义:在固定步骤确定的前提下,通过多态机制,在多个子类中对步骤的 “细节” 进行差异化实现 —— 此为模板方法模式的核心效果(后续通过代码案例深化理解)。

(1.2) 模式分类与特点

  • 分类:属于 “行为型模式”(关注对象行为流程的设计)
  • 特点:
    • 实现简单:代码结构清晰,易上手
    • 使用高频:很多程序员在不知情时已用到(如固定流程 + 子类定制)
    • 学习价值:作为设计模式入门案例,为后续复杂模式(如策略模式、工厂方法模式)打基础

(2)具体实现范例:游戏角色技能开发(核心实操环节)

(2.1)范例背景:游戏项目需求

  • 项目设定:A 公司单机闯关打斗类游戏项目组,核心角色为 “游戏策划”、“游戏程序”、“游戏美术”(仅聚焦前两者,美术与设计模式无关)。
  • 初始需求(第一版代码):
    • 主角类型:战士(特点:生命值高、抗揍,攻击力较弱)
    • 主角属性:
      • 生命值:初始 1000(为 0 时主角死亡,游戏结束)
      • 魔法值:初始 0(暂未使用,预留扩展)
      • 攻击力:初始 200(决定攻击敌人时的伤害值)
    • 新增技能:“燃烧”(核心需求)
      • 技能效果:
        1. 附近所有敌人每人失去 500 点生命值
        2. 主角自身失去 300 点生命值
        3. 播放 “燃烧” 技能特效(文字模拟,无实际美术资源)

(2.2)第一版代码:战士类单独实现

(a)代码结构
#include <iostream>

using namespace std;

namespace _nmspl
{
    //定义一个战士类
    class Warrior
    {
    public:
        //构造函数
        Warrior(int life, int magic, int attack)
            : m_life(life),m_magic(magic),m_attack(attack){}

    public:
        void JN_Burn()//技能“燃烧”
        {
            // 1. 敌人受影响(文字模拟,实际业务逻辑省略)
            cout << "敌人每人失去500点生命(相关逻辑代码略)" << endl;
            // 2. 主角自身受影响
            m_life -= 300;
            cout << "主角自身失去300点生命值" << endl;
            // 3. 播放技能特效
            cout << "播放技能「燃烧」的技能特效给玩家看" << endl;
        }
        
    private:
        //角色属性
        int m_life;//生命值
        int m_magic;//魔法值
        int m_attack;//攻击力
    };
}

int main()
{
    _nmspl::Warrior mrolebj(100, 0, 200);// 创建战士对象(生命值1000,魔法值0,攻击力200)
    //释放燃烧技能
    mrolebj.JN_Burn();

    return 0;
}
(b)测试结果
敌人每人失去500点生命(相关逻辑代码略)
主角自身失去300点生命值
播放技能「燃烧」的技能特效给玩家看
(c)代码问题
  • 扩展性差:若新增主角类型(如法师),需重新写一套类似代码,导致大量重复(属性定义、技能框架一致,仅细节不同)。
  • 维护成本高:后续修改技能框架(如新增 “技能前摇” 步骤),需修改所有角色类的代码。

(2.3)需求迭代:新增法师角色(第二版代码:重构引入父类)

(a)新增需求
  • 主角类型:法师(特点:攻击力强、生命值低、依赖魔法值)
  • 法师属性:
    • 生命值:初始 800
    • 魔法值:初始 200(释放技能消耗)
    • 攻击力:初始 300
  • 法师 “燃烧” 技能差异:
    1. 敌人每人失去 650 点生命值(比战士伤害高)
    2. 主角自身失去 100 点魔法值(而非生命值)
    3. 技能特效与战士一致(策划规定:所有角色 “燃烧” 特效相同)
(b)重构思路:提取 “战斗者” 父类(Fighter)
  • 核心逻辑:将 “所有角色共有的属性和固定流程” 放入父类,“差异化细节” 通过子类虚函数实现(多态机制)。
  • 关键共识(与策划确认):
    1. 后续将新增 “牧师” 角色,也需 “燃烧” 技能
    2. 所有角色 “燃烧” 技能的固定流程:对敌人产生影响 → 对自身产生影响 → 播放技能特效(前两步细节不同,第三步完全相同)
(c)第二版代码:父类 + 子类结构
#include <iostream>

using namespace std;

namespace _nmspl
{
    //定义一个战士类
    class Warrior
    {
    public:
        //构造函数
        Warrior(int life, int magic, int attack): m_life(life),m_magic(magic),m_attack(attack){}

    public:
        void JN_Burn()//技能“燃烧”
        {
            // 1. 敌人受影响(文字模拟,实际业务逻辑省略)
            cout << "敌人每人失去500点生命(相关逻辑代码略)" << endl;
            // 2. 主角自身受影响
            m_life -= 300;
            cout << "主角自身失去300点生命值" << endl;
            // 3. 播放技能特效
            cout << "播放技能「燃烧」的技能特效给玩家看" << endl;
        }
    private:
        //角色属性
        int m_life;//生命值
        int m_magic;//魔法值
        int m_attack;//攻击力
    };
}

namespace _nmsp2
{
    //战斗者父类
    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 JN_Burn()//技能“燃烧”
        {
            effect_enemy();//对敌人产生影响
            effect_self();//自身会产生影响
            play_effect();//播放技能"燃烧"特效
        }
    private:
        virtual void effect_enemy() {}//函数体为空,表示啥也不做,如果要求必须在子类中重新实现该函数,则可以将该函数携程纯虚函数
        virtual void effect_self() {}
        void play_effect() 
        {
            //所有主角播放的技能特效都相同,因此不用写成一个虚函数并在子类中实现
            cout << "播放技能「燃烧」的技能特效给玩家看" << endl;
        }

    protected://可能被子类访问,所以用protected修饰
        //角色属性
        int m_life;//生命值
        int m_magic;//魔法值
        int m_attack;//攻击力
    };

    //-----------------
    //“战士”类,父类为Fighter
	class F_Warrior : public Fighter
	{
	public:
		F_Warrior(int life, int magic, int attack) :Fighter(life, magic, attack) {}

    private:
        virtual void effect_enemy()//对敌人产生影响
        {
            cout << "战士主角_让每人失去500点生命,相关逻辑的代码略" << endl;
        }
        virtual void effect_self()//自身会产生影响
        {
            // 战士主角自身受影响
            cout << "主角自身失去300点生命值" << endl;
            m_life -= 300;
        }
	};
    //-----------------
//“法师”类,父类为Fighter
    class F_Mage : public Fighter
    {
    public:
        F_Mage(int life, int magic, int attack) :Fighter(life, magic, attack) {}

    private:
        virtual void effect_enemy()//对敌人产生影响
        {
            cout << "法师主角_让每人失去650点生命,相关逻辑的代码略" << endl;
        }
        virtual void effect_self()//自身会产生影响
        {
            // 法师主角自身受影响
            cout << "法师主角_自身失去100点魔法值" << endl;
            m_magic -= 100;
        }
    };
}

int main()
{
    /*
    _nmspl::Warrior mrolebj(100, 0, 200);// 创建战士对象(生命值1000,魔法值0,攻击力200)
    //释放燃烧技能
    mrolebj.JN_Burn();
    */

    _nmsp2::Fighter* prole_war = new _nmsp2::F_Warrior(1000, 0, 200);//创建战士主角,注意父类指针指向子类对象以利用多态特性
    prole_war->JN_Burn();//战士主角释放“燃烧”技能
    cout << "----------------" << endl;

    _nmsp2::Fighter* prole_magic = new _nmsp2::F_Mage(800, 200, 300);//创建法师主角,注意父类指针指向子类对象以利用多态特性
    prole_magic->JN_Burn();//法师主角释放“燃烧”技能
    cout << "----------------" << endl;
    
    //释放资源
    delete prole_war;
    delete prole_magic;


    return 0;
}
(d)测试结果
士主角_让每人失去500点生命,相关逻辑的代码略
主角自身失去300点生命值
播放技能「燃烧」的技能特效给玩家看
----------------
法师主角_让每人失去650点生命,相关逻辑的代码略
法师主角_自身失去100点魔法值
播放技能「燃烧」的技能特效给玩家看

(2.4)第二版代码的核心优势(设计模式价值体现)

  • 复用性提升:固定流程(JN_Burn)和公共逻辑(playEffect)在父类实现,子类仅需关注差异化细节(effectEnemy/effectSelf),避免代码重复。
  • 扩展性提升:新增 “牧师” 角色时,仅需创建FPriest子类,重写两个虚函数即可,无需修改父类代码(符合 “开闭原则”:对扩展开放,对修改关闭)。
  • 维护性提升:若需修改技能流程(如新增 “技能冷却判断” 步骤),仅需修改父类skillBurn函数,所有子类自动继承新流程,无需逐个修改。

(2.5)课程小结与后续预告

(a)本节课核心成果
  • 理解模板方法模式的 “固定步骤 + 差异化实现” 本质,通过 “饭馆吃饭” 类比建立直观认知。
  • 完成游戏角色技能的代码重构:从 “单一战士类” 到 “父类(Fighter)+ 子类(F_Warrior/F_Mage)” 结构,落地多态机制,体现设计模式的复用与扩展价值。
  • 明确模板方法模式的核心载体:父类中的 “模板方法”(JN_Burn)—— 定义固定流程,调用子类实现的虚函数(差异化步骤)。

第二节 引入模板方法模式

(1)设计模式核心原则(重中之重)

  • 核心逻辑:软件开发中需求变化频繁,开发人员需先寻找 “变化点” 与 “稳定点”,将二者分离,仅在变化点处应用设计模式。
  • 关键能力:抽象能力 —— 将代码按层次结构划分,这是灵活运用设计模式的前提。
  • 核心难点:学习设计模式本身不难,难在判断 “何时何地” 使用该模式

(2)从范例回顾模板方法的 “前提”

以上节课的Fighter类(父类)和F_Warrior(战士子类)、F_Mage(法师子类)为例:

  • 稳定部分:父类FighterJN_Burn(技能释放函数)是算法骨架—— 固定调用 3 个成员函数(如effect_enemyeffect_self等),源代码仅 3 行,结构完全稳定。
  • 变化部分effect_enemy(对敌人影响)和effect_self(对自身影响)的实现因角色不同而变化(如战士燃烧技能耗血、法师耗蓝),需由子类实现。
  • 模板方法的由来:稳定的JN_Burn函数被称为模板方法(成员函数 = 方法,固定结构 = 模板),这是模板方法模式命名的核心原因。

(3)早绑定与晚绑定(动态绑定)

模板方法模式依赖虚函数的晚绑定机制,二者对比:

类型 核心逻辑 示例代码(以F_Warrior为例) 确定时机
晚绑定 父类指针指向子类对象,调用虚函数时,根据 “实际对象类型” 动态确定执行的函数 Fighter* p = new F_Warrior;
p->JN_Burn();
程序运行时
早绑定 直接通过子类对象调用函数,编译时已确定执行的函数 F_Warrior w;
w.JN_Burn();
程序编译时
  • 模板方法模式的核心是晚绑定—— 通过父类指针调用模板方法,间接触发子类的虚函数实现,实现 “统一骨架、差异化执行”。

(4)模板方法模式的定义(官方意图)

  • 模式意图:定义一个操作的算法骨架(稳定部分,如JN_Burn),将部分步骤(变化部分,如effect_enemy延迟到子类中实现
  • 最终目的:在 “整体流程稳定” 的前提下,保留 “局部步骤变化” 的灵活性,实现 “稳定与变化的隔离”。
  • 经典总结:设计模式的本质是 “在变化与稳定间找隔离点,分离二者以管理变化”—— 若全稳定或全变化,无需使用设计模式。

(5)反向控制结构:好莱坞法则

  • 核心概念:模板方法模式会导致 “反向控制”——父类的成员函数调用子类的成员函数(如Fighter::JN_Burn调用F_Warrior::effect_self),这与常规 “子类调用父类” 逻辑相反。
  • 好莱坞法则:形象描述这种反向控制 ——“不要来调用我,我会调用你”(类比导演对演员:演员不用找导演,导演需要时会联系演员)。
  • 底层原理:虚函数的晚绑定机制 —— 父类指针指向子类对象时,模板方法中调用的虚函数会自动匹配子类实现(可通过断点跟踪F11验证)。

(6)实际开发分工建议

大型项目中模板方法模式的典型分工:

  • 父类(如Fighter:由项目经理 / 主程序开发,定义模板方法和虚函数(确定算法骨架)。
  • 子类(如F_Warrior:由普通开发者 / 第三方开发商实现,仅需重写父类的虚函数(关注变化点)。
  • 开发建议:普通开发者无需先深究父类骨架细节,优先按需求实现子类虚函数,保证任务交付;后续有时间再研究父类逻辑,避免 “只见树木不见森林”。

第三节 模板方法模式的 UML 图

(1)UML 基础认知

  • 全称:Unified Modeling Language(统一建模语言),是可视化描述类结构与类间关系的工具。
  • 核心价值:将代码以图形形式呈现,便于理解全局逻辑(尤其复杂类层次)。

(2)模板方法模式的 UML 图规范

Fighter(父类)、F_Warrior(子类)、F_Mage(子类)为例,UML 图的关键要素:

图形元素 含义与规范
类的结构 每个类用 “长方形” 表示,分 3 个区域(从上到下):1. 类名(如Fighter)2. 成员变量3. 成员函数
访问修饰符 用符号表示:+ = public(如+ JN_Burn()# = protected(如# m_life- = private(如- m_mana
虚函数标识 父类中的虚函数(变化点)用斜体表示(如# effect_enemy()),子类虚函数可省略斜体
类间关系 子类指向父类的 “带空心三角箭头” 表示继承(如F_WarriorFighter
稳定 / 变化凸显 模板方法(稳定)用 “放大字体”(如JN_Burn),虚函数(变化)用 “缩小字体”(如effect_enemy
  • 注意:UML 图无需严格统一细节(如线条粗细),核心是清晰表达 “类结构” 和 “继承关系”。

第四节 程序代码进一步完善及应用联想

(1)问题场景:测试反馈的功能漏洞

上节课的代码存在逻辑缺陷:战士 / 法师即使 “资源不足”(如战士血 < 300、法师蓝 < 100),仍能释放JN_Burn技能,需修复。

(2) 解决方案:新增 “技能可用性判断”(钩子方法)

(2.1)核心思路

在父类Fighter中新增纯虚函数canUse_skill()(判断是否能释放技能),作为 “钩子方法”,由子类实现差异化判断逻辑;模板方法JN_Burn先调用该函数,若返回false则直接退出(不执行技能)。

(2.2)关键代码实现

  • 父类Fighter(新增纯虚函数)

    //战斗者父类
    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 JN_Burn() //技能“燃烧”,模板方法
        {
            if (canUseJN() == false) //如果不能使用该技能,则直接返回
                return; 
            effect_enemy(); //对敌人产生的影响
            effect_self(); //对主角自身产生的影响
            play_effect(); //播放技能“燃烧”的技能特效
        }
    private:
        virtual void effect_enemy() {} //函数体为空,表示啥也不做,如果要求必须在子类中重新实现该虚函数,则可以将该函数写成纯虚函数。
        virtual void effect_self() {}
        void play_effect()
        {
            cout << "播放技能\"燃烧\"的技能特效给玩家看" << endl; //所有主角播放的技能特效都相同,因此不用写成一个虚函数并在子类中实现技能特效的播放。
        }
        virtual bool canUseJN() = 0; //判断是否能使用技能“燃烧”,这是个纯虚函数声明,子类中必须重新实现canUseJN。
    protected: //可能被子类访问,所以用protected修饰
        //角色属性
        int m_life; //生命值
        int m_magic; //魔法值
        int m_attack;  //攻击力
    };
    
  • 子类F_Warrior(实现钩子方法)

    //“战士”类,父类为Fighter
    class F_Warrior :public Fighter
    {
    public:
        F_Warrior(int life, int magic, int attack) :Fighter(life,magic,attack) {}
    
    private:
        //对敌人产生的影响
        virtual void effect_enemy() 
        {
            cout << "战士主角_让所有敌人每人失去500点生命,相关逻辑代码这里略......" << endl;
        } 
        //对主角自身产生的影响
        virtual void effect_self()
        {
            cout << "战士主角_自身失去300点生命值" << endl;
            m_life -= 300;			
        }
        virtual bool canUseJN()
        {
            if (m_life < 300) //生命值不够300点,不能使用技能“燃烧”
                return false;
            return true;
        }
    };
    
  • 子类F_Mage(实现钩子方法)

    //“法师”类,父类为Fighter
    class F_Mage :public Fighter
    {
    public:
        F_Mage(int life, int magic, int attack) :Fighter(life, magic, attack) {}
    
    private:
        //对敌人产生的影响
        virtual void effect_enemy()
        {
            cout << "法师主角_让所有敌人每人失去650点生命,相关逻辑代码这里略......" << endl;
        }
        //对主角自身产生的影响
        virtual void effect_self()
        {
            cout << "法师主角_自身失去100点魔法值" << endl;
            m_magic -= 100;
        }
        virtual bool canUseJN()
        {
            if (m_magic < 100) //魔法值不够100点,不能使用技能“燃烧”
                return false;
            return true;
        }
    };
    

(2.3)测试验证

  • 案例:创建生命值 = 50 的战士(FWarrior w; w.m_life=50;),调用w.JN_Burn()—— 因canUseJN()返回false,技能不执行,无任何输出,修复漏洞。

    // MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    //公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
    
    #include <iostream>
    
    #ifdef _DEBUG   //只在Debug(调试)模式下
    #ifndef DEBUG_NEW
    #define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符
    #define new DEBUG_NEW
    #endif
    #endif
    
    //#include <boost/type_index.hpp>
    using namespace std;
    //#pragma warning(disable : 4996) 
    
    namespace _nmsp1
    {
    	//定义一个“战士”类
    	class Warrior
    	{
    	public:
    		//构造函数
    		Warrior(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack)
    		{
    		}
    		//...一些其他成员函数,不重要,不深究
    
    	public:
    		void JN_Burn() //技能“燃烧”
    		{
    			cout << "让所有敌人每人失去500点生命,相关逻辑代码这里略......" << endl;
    			cout << "主角自身失去300点生命值" << endl;
    			m_life -= 300;
    			cout << "播放技能\"燃烧\"的技能特效给玩家看" << endl;
    		}
    	private:
    		//角色属性
    		int m_life; //生命值
    		int m_magic; //魔法值
    		int m_attack;  //攻击力
    	};
    
    }
    namespace _nmsp2
    {
    	//战斗者父类
    	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 JN_Burn() //技能“燃烧”,模板方法
    		{
    			if (canUseJN() == false) //如果不能使用该技能,则直接返回
    				return;
    			effect_enemy(); //对敌人产生的影响
    			effect_self(); //对主角自身产生的影响
    			play_effect(); //播放技能“燃烧”的技能特效
    		}
    	private:
    		virtual void effect_enemy() {} //函数体为空,表示啥也不做,如果要求必须在子类中重新实现该虚函数,则可以将该函数写成纯虚函数。
    		virtual void effect_self() {}
    		void play_effect()
    		{
    			cout << "播放技能\"燃烧\"的技能特效给玩家看" << endl; //所有主角播放的技能特效都相同,因此不用写成一个虚函数并在子类中实现技能特效的播放。
    		}
    		virtual bool canUseJN() = 0; //判断是否能使用技能“燃烧”,这是个纯虚函数声明,子类中必须重新实现canUseJN。
    	protected: //可能被子类访问,所以用protected修饰
    		//角色属性
    		int m_life; //生命值
    		int m_magic; //魔法值
    		int m_attack;  //攻击力
    	};
    
    	//-------------------------
    	//“战士”类,父类为Fighter
    	class F_Warrior :public Fighter
    	{
    	public:
    		F_Warrior(int life, int magic, int attack) :Fighter(life, magic, attack) {}
    
    	private:
    		//对敌人产生的影响
    		virtual void effect_enemy()
    		{
    			cout << "战士主角_让所有敌人每人失去500点生命,相关逻辑代码这里略......" << endl;
    		}
    		//对主角自身产生的影响
    		virtual void effect_self()
    		{
    			cout << "战士主角_自身失去300点生命值" << endl;
    			m_life -= 300;
    		}
    		virtual bool canUseJN()
    		{
    			if (m_life < 300) //生命值不够300点,不能使用技能“燃烧”
    				return false;
    			return true;
    		}
    	};
    	//-------------------------
    	//“法师”类,父类为Fighter
    	class F_Mage :public Fighter
    	{
    	public:
    		F_Mage(int life, int magic, int attack) :Fighter(life, magic, attack) {}
    
    	private:
    		//对敌人产生的影响
    		virtual void effect_enemy()
    		{
    			cout << "法师主角_让所有敌人每人失去650点生命,相关逻辑代码这里略......" << endl;
    		}
    		//对主角自身产生的影响
    		virtual void effect_self()
    		{
    			cout << "法师主角_自身失去100点魔法值" << endl;
    			m_magic -= 100;
    		}
    		virtual bool canUseJN()
    		{
    			if (m_magic < 100) //魔法值不够100点,不能使用技能“燃烧”
    				return false;
    			return true;
    		}
    	};
    }
    
    int main()
    {
    	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口
    
    	//第二章 模板方法(Template Method)模式
    	//饭馆吃饭: 点餐(粤菜,鲁菜)->食用->结账(现金,信用卡,微信),因为这几个步骤是固定的所以作为以样板。
    	//在固定步骤确定的情况下,通过多态机制在多个子类中对每个步骤的细节进行差异化实现,这就是模板方法模式能够达到的效果。
    	//模板方法模式:行为型模式。
    
    	//(1)一个具体实现范例的逐步重构
    	//A公司有一个小游戏项目组——开发单机闯关打斗类游戏(类似街机打拳类游戏)。
    	//一个游戏项目组最少需要三名担任不同角色的员工组成:游戏策划,游戏程序,游戏美术。
    	  //a)游戏策划:简称策划 ,负责提出游戏的各种玩法需求,确定游戏中各种数值比如人物(敌人)的生命值、魔法值。
    	  //b)游戏程序:简称程序,需要与游戏策划紧密配合通过代码来实现游戏策划要求的各种游戏功能。
    	  //c)游戏美术:角色设计,道具设计,游戏特效等等。
    
    	//游戏策划需求:游戏主角是个战士(攻击力不够强,生命值比较多,抗揍),主角通过不断的往前走来闯关,遇到敌人就进行攻击,
    		 //敌人也会反击,敌人也会距离近时主动攻击主角。
    		 //主角:生命值——1000(为0时主角死亡,游戏结束),魔法值——0(暂时用不上,保留),攻击力——200(打敌人一下敌人失去多少点声明值) 三个属性。
    		 //技能“燃烧”——使用该技能可以使附近所有敌人每人失去500点生命值,但是主角自身也会损失掉300点生命值。
    
    	//增加 法师 作为主角(攻击力很强,生命值比较少,不抗揍)
    		//主角:生命值——800,魔法值——200(暂时用不上,保留),攻击力——300  三个属性。 
    		//技能“燃烧”——使用该技能可以使附近所有敌人每人失去650点生命值,但是主角自身会损失掉100点魔法值。
    
    	//将要增加 牧师 作为主角
    	//每个主角都有一个叫做 燃烧 的技能。每个主角释放 燃烧技能时效果各不相同。有两点是肯定不变的:对主角自身会产生影响,对敌人会产生影响。
    
    	//战士和法师释放 燃烧 技能表现是不同的,这种不同的表现主要是通过F_Warrior和F_Mage子类中的effect_enemy和effect_self虚函数来体现的。
    
    	//(2)引入模板方法(Template Method)模式
    	//软件开发中需求变化频繁的,开发人员要尝试寻找变化点,把变化部分和稳定部分分离开来,在变化的地方应用设计模式。
    	//学习设计模式并不难,难的是在何时何地运用该模式。
    	//设计模式中往往会把 成员函数 说成是 算法。
    	//晚绑定:代码执行时才知道具体要执行哪个虚函数。
    	//早绑定:编译时就能确定执行的是哪个子类(F_Warrior或者F_Mage中的effect_enemy或者effect_self)。
    	//模板方法模式的定义(实现意图):定义了一个操作中的算法的骨架(稳定部分),而将一些步骤延迟到子类中去实现(父类中定义虚函数,子类中实现/重写这个虚函数),
    		  //从而达到在整体稳定的情况下能够产生一些变化的目的。
    	//设计模式的经典总结:设计模式的作用就是在变化和稳定中间寻找隔离点,分离稳定和变化,从而来管理变化。
    	//模板方法模式也被认为导致了一种反向控制结构——这种结构被称为好莱坞法则——不要来调用我,我会去调用你。
    
    	//(3)模板方法(Template Method)模式的UML图
    	//UML:Unified Modeling Language:统一建模语言。
    	//UML:一种工具,通过该工具可以绘制一个类的结构图和类与类之间的关系。这种把所编写的代码以图形方式呈现对于代码的全局理解和掌握好处巨大。
    
    	//(4)程序代码的进一步完善及应用联想
    	//钩子方法:子类勾住父类从而反向控制父类行为的意思,因此起名为钩子方法。
    	//MFC框架(微软基础类库):通过MFC创建一个基于对话框的应用程序。自动调用OnInitDialog成员函数(相当于effect_enemy或者effect_self这样的虚函数)。
    
    	//车间能够装配很多零件。如果零件的装配工序非常固定,工序细节有微小变化,就可以
    	   //针对零件创建一个父类,其中零件装配工序(成员函数)就非常适合采用模板方法模式来实现,而处理某道工序的细节可以直接放在子类(针对某个具体零件的类)虚函数中进行。
    
    	/*
    	_nmsp1::Warrior mroleobj(1000, 0, 200); //创建主角
    	mroleobj.JN_Burn(); //主角释放“燃烧”技能
    	*/
    
    	/*
    	_nmsp2::Fighter* prole_war = new _nmsp2::F_Warrior(1000, 0, 200); //创建战士主角,注意这里是父类指针指向子类对象以利用多态特性。
    	prole_war->JN_Burn(); //战士主角释放“燃烧”技能,调用的是F_Warrior类的effect_enemy和effect_self。
    
    	cout << "---------------------------" << endl; //分割线,以便更醒目的显示信息
    	_nmsp2::Fighter* prole_mag = new _nmsp2::F_Mage(800,200, 300); //创建法师主角,注意这里是父类指针指向子类对象以利用多态特性。
    	prole_mag->JN_Burn(); //法师主角释放“燃烧”技能
    
    	//释放资源
    	delete prole_war;
    	delete prole_mag;
    	*/
    
    	////早绑定
    	//_nmsp2::F_Warrior role_war(1000, 0, 200);
    	//role_war.JN_Burn(); //早绑定
    
    
    	_nmsp2::Fighter* prole_war2 = new _nmsp2::F_Warrior(50, 0, 200);//创建生命值只有50的战士主角
    	prole_war2->JN_Burn(); //该战士无法成功释放“燃烧”技能,不输出任何结果。
    						   //钩子方法
    
    	delete prole_war2;
    
    
    	return 0;
    }
    
    // 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
    // 调试程序: F5 或调试 >“开始调试”菜单
    
    // 入门使用技巧: 
    //   1. 使用解决方案资源管理器窗口添加/管理文件
    //   2. 使用团队资源管理器窗口连接到源代码管理
    //   3. 使用输出窗口查看生成输出和其他消息
    //   4. 使用错误列表窗口查看错误
    //   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
    //   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
    

(3)钩子方法的含义

  • 定义canUseJN()这类 “子类可控制父类行为” 的纯虚函数,被称为钩子方法(Hook Method)。
  • 作用:子类通过钩子方法的返回值,反向决定父类模板方法的执行流程(如 “是否执行技能”),进一步增强模板方法模式的灵活性。

(4)模板方法模式的应用联想

模板方法模式的核心是 “固定骨架、延迟变化”,常见应用场景:

  1. 框架开发:如 MFC 框架 —— 框架定义程序的生命周期骨架(如对话框创建、消息循环),开发者通过子类重写虚函数(如OnPaint)实现自定义界面。
  2. 工业流程:零件装配 —— 装配工序(骨架)固定(如 “零件 A→零件 B→零件 C”),某道工序的细节(如零件 A 的打磨方式)由子类实现。
  3. 生活场景:饭馆吃饭 —— 流程骨架固定(“点餐→用餐→结账”),点餐内容(中餐 / 西餐)、用餐方式(堂食 / 外卖)由 “子类”(具体顾客需求)决定。

第五节 本节核心总结

  1. 模板方法模式三要素:稳定的算法骨架(模板方法)、变化的步骤(虚函数)、晚绑定机制(父类指针调用子类虚函数)。
  2. 关键概念:分离稳定与变化、晚绑定、好莱坞法则、钩子方法。
  3. 开发建议:优先关注 “变化点” 的实现(子类虚函数),再深究 “稳定骨架”(父类模板方法),符合实际项目分工逻辑。

参考资料来源:王健伟

posted @ 2025-12-05 10:41  CodeMagicianT  阅读(2)  评论(0)    收藏  举报