第四章 策略模式
第四章 策略模式
第一节 一个具体实现范例的逐步重构
1.1课程引入与需求背景
1.1.1课程衔接
上一章完成工厂模式、原型模式、建造者模式的学习,本章进入行为型设计模式—— 策略模式。策略模式的核心思想与模板方法模式类似,均以 “扩展方式支持未来变化”,本章通过具体范例重构引入模式,下一节将讲解依赖倒置原则。
1.1.2业务需求
以单机闯关打斗游戏为场景,需为游戏主角(战士、法师)添加补血道具功能:
- 道具类型(药品):补血丹(+200 生命值)、大还丹(+300 生命值)、守护丹(+500 生命值)
- 触发条件:主角走到特定场景或击杀大型怪物后,触碰道具即可补血
- 技术基础:已存在
Fighter(战斗者父类)、F_Warrior(战士子类)、F_Mage(法师子类)
1.2原始实现方案及问题分析
1.2.1原始代码实现
(1)Fighter.h 核心部分
#ifndef __RIGHTER__
#define __RIGHTER__
// 定义道具枚举
enum ItemAddlife
{
LF_BXD, // 补血丹
LF_DHD, // 大还丹
LF_SHD // 守护丹
};
//战斗者父类
class Fighter
{
public:
Fighter(int life, int magic, int attack) :m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Fighter() {}
public:
// 吃药补血的核心函数
void UseItem(ItemAddlife djtype)
{
if (djtype == LF_BXD)
{
m_life += 200; // 补血丹+200
} else if (djtype == LF_DHD)
{
m_life += 300; // 大还丹+300
} else if (djtype == LF_SHD)
{
m_life += 500; // 守护丹+500
}
// 未来扩展功能需添加更多if-else
}
// 其他成员(构造、析构、成员变量等)
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)
{
cout << "战士角色创建成功!" << endl;
}
~F_Warrior()
{
cout << "战士角色析构" << endl;
}
};
// 法师子类(继承Fighter)
class F_Mage : public Fighter
{
public:
// 构造函数:调用父类构造初始化
F_Mage(int life, int magic, int attack) : Fighter(life, magic, attack)
{
cout << "法师角色创建成功!" << endl;
}
~F_Mage()
{
cout << "法师角色析构" << endl;
}
};
#endif
(2)MyProject.cpp
// MyProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
//公众号:程序员速成 ,内含一辈子都让你感激自己的优质视频教程,欢迎关注
#include <iostream>
#include "Fighter.h"
#include "ItemStrategy.h"
#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
{
}
int main() {
// 初版直接创建Fighter对象(无战士/法师子类)
Fighter* prole_war = new Fighter(1000, 0, 200); // 主角:1000血、0蓝、200攻击
// 测试使用三种道具(按初版需求顺序测试)
cout << "\n=== 开始使用道具 ===" << endl;
prole_war->UseItem(LF_DHD); // 先使用大还丹
// 释放资源(避免内存泄漏)
delete prole_war;
return 0;
}
1.2.2原始方案的三大核心问题
- 违反开闭原则:新增道具时,需同时修改
enum枚举和UseItem中的if-else分支,直接修改原有代码 - 代码复用性差:若怪物也需使用补血道具,需复制
UseItem的所有判断逻辑到怪物类,导致代码冗余 - 逻辑扩展性差:若道具功能升级(如补血 + 解毒、狂暴状态下额外补血 / 补蓝),
UseItem会堆积大量逻辑,函数臃肿难以维护
1.3策略模式重构方案(核心重点)
1.3.1策略模式核心思想
将UseItem中每个if-else分支(即 “每种道具的补血算法”)封装为独立的策略类,通过抽象策略类统一接口,让算法可相互替换,最终解决原始方案的问题。
1.3.2重构步骤与代码实现
步骤 1:创建抽象策略类(ItemStrategy.h)
定义所有策略(道具)的公共接口,声明UseItem纯虚函数(参数为Fighter*,用于访问主角的生命值数据):
#ifndef _ITEMSTRATEGY__
#define _ITEMSTRATEGY__
// 前向声明Fighter类(因策略类需访问Fighter的接口)
class Fighter;
// 抽象策略类:所有补血道具的父类
class ItemStrategy
{
public:
// 纯虚函数:定义补血算法的接口
virtual void UseItem(Fighter* mainobj) = 0;
virtual ~ItemStrategy() {} // 虚析构函数,确保子类析构正常
};
#endif
步骤 2:实现具体策略类(ItemStrategy.h 续)
每个具体策略类对应一种道具,实现抽象策略类的UseItem接口,封装专属补血逻辑:
#ifndef _ITEMSTRATEGY__
#define _ITEMSTRATEGY__
//道具策略类的父类
class ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj) = 0;
virtual ~ItemStrategy() {}
};
//补血丹策略类
class ItemStrategy_BXD :public ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj)
{
mainobj->SetLife(mainobj->GetLife() + 200); //补充200点生命值
}
};
//大还丹策略类
class ItemStrategy_DHD :public ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj)
{
mainobj->SetLife(mainobj->GetLife() + 300); //补充300点生命值
}
};
//守护丹策略类
class ItemStrategy_SHD :public ItemStrategy
{
public:
virtual void UseItem(Fighter* mainobj)
{
mainobj->SetLife(mainobj->GetLife() + 500); //补充500点生命值
}
};
#endif
步骤 3:改造环境类(Fighter.h)
Fighter作为环境类,不再直接包含补血逻辑,而是通过持有抽象策略类的指针,动态切换策略(道具):
#ifndef __RIGHTER__
#define __RIGHTER__
class ItemStrategy; // 前向声明策略类
class Fighter
{
public:
Fighter(int life, int magic, int attack)
: m_life(life), m_magic(magic), m_attack(attack) {}
virtual ~Fighter() {}
// 核心接口:设置当前使用的道具策略
void SetItemStrategy(ItemStrategy* strategy)
{
itemstrategy = strategy;
}
// 核心接口:执行补血(调用当前策略的算法)
void UseItem()
{
if (itemstrategy != nullptr)
{
// 传入this指针,让策略类访问当前对象的生命值
itemstrategy->UseItem(this);
}
}
// 提供生命值的读写接口(供策略类调用)
int GetLife() { return m_life; }
void SetLife(int life) { m_life = life; }
protected:
int m_life; // 生命值
int m_magic; // 魔法值
int m_attack; // 攻击力
// 持有抽象策略类的指针(C++11支持直接初始化 nullptr)
ItemStrategy* itemstrategy = nullptr;
};
// 战士子类(无修改,继承Fighter的所有接口)
class F_Warrior : public Fighter
{
public:
F_Warrior(int life, int magic, int attack) : Fighter(life, magic, attack) {}
};
// 法师子类(无修改)
class F_Mage : public Fighter
{
public:
F_Mage(int life, int magic, int attack) : Fighter(life, magic, attack) {}
};
#endif
步骤 4:实现环境类成员函数(Fighter.cpp)
#include <iostream>
#include "Fighter.h"
#include "ItemStrategy.h"
using namespace std;
// 实现设置策略的接口
void Fighter::SetItemStrategy(ItemStrategy* strategy)
{
itemstrategy = strategy;
}
// 实现使用道具的接口
void Fighter::UseItem()
{
if (itemstrategy != nullptr)
{
itemstrategy->UseItem(this);
}
}
// 实现生命值读写接口
int Fighter::GetLife()
{
return m_life;
}
void Fighter::SetLife(int life)
{
m_life = life;
}
步骤 5:主函数调用(MyProject.cpp)
客户端通过 “创建策略对象→设置策略→执行策略” 的流程使用道具,支持动态切换:
#include <iostream>
#include "Fighter.h"
#include "ItemStrategy.h"
int main()
{
// 1. 创建主角(战士,初始生命值1000)
Fighter* prole_war = new F_Warrior(1000, 0, 200);
// 2. 吃大还丹(+300)
ItemStrategy* dhd_strategy = new ItemStrategy_DHD(); // 创建大还丹策略
prole_war->SetItemStrategy(dhd_strategy); // 设置当前策略为大还丹
prole_war->UseItem(); // 执行补血(生命值变为1300)
// 3. 吃补血丹(+200)
ItemStrategy* bxd_strategy = new ItemStrategy_BXD(); // 创建补血丹策略
prole_war->SetItemStrategy(bxd_strategy); // 切换策略为补血丹
prole_war->UseItem(); // 执行补血(生命值变为1500)
// 4. 释放资源
delete dhd_strategy;
delete bxd_strategy;
delete prole_war;
return 0;
}
1.3.3关键代码说明
- 策略类与环境类的耦合:策略类
UseItem参数为Fighter*,是因为策略需要访问主角的生命值数据(GetLife/SetLife),这种耦合是合理且必要的 - 前向声明:
Fighter.h中声明ItemStrategy,ItemStrategy.h中声明Fighter,解决跨文件相互引用问题 - 动态切换策略:通过
SetItemStrategy可随时更换道具,无需修改Fighter类代码,符合开闭原则
1.3.4策略模式的核心定义与 UML 类图
(1)模式定义
定义一系列算法(如三种补血逻辑),将每个算法封装到独立的具体策略类中,作为抽象策略类的子类,使得算法可相互替换,客户端可根据需求动态选择使用哪种算法。
(2)UML 类图

(3)三大核心角色
| 角色名称 | 职责描述 | 本范例对应类 |
|---|---|---|
| Context(环境类) | 维持抽象策略类的指针 / 引用,提供接口供客户端设置策略、执行算法 | Fighter类 |
| Strategy(抽象策略类) | 定义所有具体策略的公共接口(纯虚函数),是所有策略类的父类 | ItemStrategy类 |
| ConcreteStrategy(具体策略类) | 继承抽象策略类,实现具体的算法逻辑(如每种道具的补血规则) | ItemStrategy_BXD等 3 个类 |
1.3.5策略模式的优缺点
(1)优点
- 符合开闭原则:新增道具时,只需新增具体策略类(如
ItemStrategy_XYD),无需修改Fighter或原有策略类,以扩展支持变化 - 消除冗余分支:替代大量不稳定的
if-else/switch语句(策略模式是 “分支杀手”),代码结构更清晰 - 算法复用性高:若怪物类需使用补血道具,只需让怪物类提供
GetLife/SetLife接口,即可直接复用现有策略类 - 替代继承方案:无需通过继承
Fighter生成 “战士 + 补血丹”“战士 + 大还丹” 等子类,通过切换策略即可改变对象行为
(2)缺点
- 策略类数量增多:每种算法对应一个类,若道具数量极多,会导致类的数量膨胀
- 客户端需了解策略:调用者(如
main函数)需熟知所有策略类的功能,才能选择合适的策略(如知道ItemStrategy_DHD是大还丹)
1.3.6适用场景总结
- 当代码中存在大量不稳定的
if-else/switch分支(如频繁新增的功能选项、道具、规则) - 当需要动态切换算法(如主角战斗时切换不同攻击策略、道具效果)
- 当算法需要被多个不同对象复用(如主角和怪物共用补血道具逻辑)
注意事项
- 若分支逻辑稳定不变(如一周七天、四季循环),无需使用策略模式,直接用
if-else更简洁 - 策略类与环境类的耦合需适度:若策略需要环境类的大量数据,可考虑封装数据传输对象(DTO),减少直接依赖
第二节 依赖倒置原则
1.1依赖倒置原则基础信息
1.1.1基本标识与核心地位
- 英文名称:Dependency Inversion Principle,简称DIP
- 核心定位:是面向对象设计的主要实现方法,同时也是实现开闭原则(对扩展开放、对修改关闭)的重要途径,可有效降低高层组件与低层组件间的耦合度,且贯穿于绝大部分设计模式。
1.1.2核心定义
高层组件不应该依赖于低层组件(具体实现类),两者都应该依赖于抽象层。
- 术语解析
- 高层组件:指业务逻辑代码(如
main函数中主角击杀怪物的调用代码),负责实现核心业务流程。 - 低层组件:指具体功能实现类(如
M_Undead、M_Element等具体怪物类),提供基础功能。 - 抽象层:指抽象父类或接口(如
Monster类),作为高层和低层组件的依赖中介,定义统一接口。
- 高层组件:指业务逻辑代码(如
1.2代码案例对比分析(怪物击杀场景)
为体现 DIP 的作用,通过两种实现方案对比说明,对应代码中_nmsp1(违背 DIP)和_nmsp2(遵循 DIP)两个命名空间的实现。
1. 2.1传统写法(_nmsp1命名空间,违背 DIP)
(1)代码实现
namespace _nmsp1
{
class M_Undead //亡灵类怪物
{
public:
void getinfo()
{
cout << "这是一只亡灵类怪物" << endl;
}
//......其他代码略
};
class M_Element //元素类怪物
{
public:
void getinfo()
{
cout << "这是一只元素类怪物" << endl;
}
//......其他代码略
};
class M_Mechanic //机械类怪物
{
public:
void getinfo()
{
cout << "这是一只机械类怪物" << endl;
}
//......其他代码略
};
//战士主角
class F_Warrior
{
public:
void attack_enemy_undead(M_Undead* pobj) //攻击亡灵类怪物
{
//进行攻击处理......
pobj->getinfo(); //可以调用亡灵类怪物相关的成员函数
}
public:
void attack_enemy_element(M_Element* pobj) //攻击元素类怪物
{
//进行攻击处理......
pobj->getinfo(); //可以调用元素类怪物相关的成员函数
}
//其他代码略......
};
}
(2)调用逻辑与问题
在main函数中调用时,需直接创建具体怪物对象并调用对应攻击接口:
_nmsp1::M_Undead* pobjud = new _nmsp1::M_Undead();
_nmsp1::F_Warrior* pobjwar = new _nmsp1::F_Warrior();
pobjwar->attack_enemy_undead(pobjud); // 攻击亡灵怪物
_nmsp1::M_Element* pobjelm = new _nmsp1::M_Element();
pobjwar->attack_enemy_element(pobjelm); // 攻击元素怪物
核心问题
- 高层组件(
F_Warrior)直接依赖低层组件(M_Undead、M_Element),耦合度极高。 - 新增怪物类型(如机械类
M_Mechanic)时,需为F_Warrior新增对应的攻击接口(如attack_enemy_mechanic),违背开闭原则,且代码冗余、扩展性差。
1.2.2遵循 DIP 的重构写法(_nmsp2命名空间)
(1)抽象层定义(Monster类)
先定义所有怪物的抽象父类,作为依赖的抽象层:
namespace _nmsp2
{
// 抽象层:所有怪物的父类
class Monster
{
public:
virtual void getinfo() = 0; // 纯虚函数,定义统一接口
virtual ~Monster() {} // 父类析构函数需为虚函数
};
}

(2)具体低层组件(继承抽象层)

让所有具体怪物类继承抽象层,实现统一接口:
namespace _nmsp2
{
class Monster //作为所有怪物类的父类(抽象层)
{
public:
virtual void getinfo() = 0; //纯虚函数
virtual ~Monster() {} //做父类时析构函数应该为虚函数
};
class M_Undead :public Monster // 亡灵类怪物
{
public:
virtual void getinfo() override
{
cout << "这是一只亡灵类怪物" << endl;
}
};
class M_Element :public Monster // 元素类怪物
{
public:
virtual void getinfo() override
{
cout << "这是一只元素类怪物" << endl;
}
};
class M_Mechanic :public Monster // 机械类怪物
{
public:
virtual void getinfo() override
{
cout << "这是一只机械类怪物" << endl;
}
};
}
(3)高层组件重构(依赖抽象层)
修改F_Warrior类,使其依赖抽象层而非具体实现类,统一攻击接口:
namespace _nmsp2
{
class F_Warrior
{
public:
// 攻击接口依赖抽象层Monster,而非具体怪物类
void attack_enemy(Monster* pobj)
{
pobj->getinfo(); // 利用多态调用具体怪物的接口
}
};
}
(4)调用逻辑与优势
main函数中调用时,通过抽象层指针指向具体怪物对象,实现业务逻辑:
_nmsp2::Monster* pobjud = new _nmsp2::M_Undead();
_nmsp2::F_Warrior* pobjwar = new _nmsp2::F_Warrior();
pobjwar->attack_enemy(pobjud); // 攻击亡灵怪物
_nmsp2::Monster* pobjelm = new _nmsp2::M_Element();
pobjwar->attack_enemy(pobjelm); // 攻击元素怪物
核心优势
- 高层组件(
F_Warrior)和低层组件(各类怪物)均依赖抽象层(Monster),解除了直接耦合。 - 新增怪物类型时,仅需新增继承
Monster的具体类,无需修改F_Warrior的攻击接口,符合开闭原则。 - 业务逻辑仅需关注抽象层
Monster,无需知晓具体怪物类的细节,降低了维护成本。
1.2.3DIP 在策略模式中的体现
策略模式的实现天然符合依赖倒置原则,以第 4 章策略模式的道具系统为例:
- 抽象层:
ItemStrategy(抽象策略类),定义统一的道具使用接口UseItem。 - 低层组件:
ItemStrategy_BXD(补血丹)、ItemStrategy_DHD(大还丹)等具体策略类,继承抽象层并实现接口。 - 高层组件:
Fighter(战斗者类,环境类),依赖抽象策略类ItemStrategy,而非具体策略子类。
当新增补血道具时,只需新增具体策略子类,无需修改Fighter类的逻辑,既遵循 DIP,也满足开闭原则。
1.2.4依赖倒置原则的核心意义
- 依赖关系倒置:传统结构化编程是 “高层依赖低层”,DIP 要求 “高低层均依赖抽象”,实现了依赖方向的倒置。
- 面向接口 / 抽象编程:核心是不针对具体实现类编程,而是针对抽象层编程,提升代码的灵活性和扩展性。
- 解耦核心手段:通过抽象层隔离高层与低层的直接关联,使得低层组件的变更不会影响高层业务逻辑。
参考资料来源:王健伟

浙公网安备 33010602011771号