设计模式小记
今天学习一些设计模式的内容,这个其实也没有啥可参考的内容,主要就是问AI,等我看完相关的书籍再来补充,这里主要结合实际的代码来讲解思想和应用场景。
所谓的设计模式,是指我们如何去设计代码,当我们面对大体量的代码时,设计模式会非常重要。设计模式本身不是代码模板,它是业界沉淀下来、针对高频软件难题的标准解决方案,本质是一套场景化的设计思路工具箱。遇到特定耦合、对象创建、流程管控、接口兼容等工程问题时,我们可以选用对应的模式规范代码结构,规避混乱的 if-else、无限继承、紧耦合、难以扩展等坏味道,它不是强制语法,而是权衡取舍后的架构经验。
在我们开始介绍设计模式前,我们需要先对设计原则有一些基本的了解:
| 原则简称 | 全称 | 核心定义 | 通俗理解 | 典型反面案例 | 相关设计模式 |
|---|---|---|---|---|---|
| SRP | 单一职责原则Single Responsibility Principle | 一个类仅有一个引起它变化的原因 | 一个类只负责一件事;职责尽量收敛 | 用户类同时实现用户 CRUD、文件存储、日志打印 | 几乎所有模式底层遵循;命令模式、职责链模式 |
| OCP | 开闭原则Open Closed Principle | 对扩展开放,对修改关闭 | 新增功能尽量新增类 / 代码,少改动稳定旧代码 | 新增算法时,直接在原有函数堆大量 if-else | 策略、装饰器、工厂方法、模板方法 |
| LSP | 里氏替换原则Liskov Substitution Principle | 子类可以完全替代父类,不破坏原有程序行为 | 继承不能篡改父类语义;父类出现地方,子类能无缝顶替 | 正方形继承长方形,重写宽高方法破坏父类逻辑 | 模板方法、工厂方法(所有依赖继承多态的模式) |
| ISP | 接口隔离原则Interface Segregation Principle | 客户端不依赖不需要的接口;优先多个小接口,拒绝臃肿大接口 | 不要强迫类实现一堆无用空方法 | 一个巨量通用接口,包含文件、网络、数据库所有方法 | 适配器模式、外观模式 |
| DIP | 依赖倒置原则Dependency Inversion Principle | 高层、低层模块都依赖抽象;抽象不依赖细节,细节依赖抽象 | 面向接口 / 抽象编程,不要直接 new 具体实现 | 业务逻辑直接硬编码依赖 MySQL 具体类,无法切换数据库 | 工厂系列模式、策略模式 |
| LoD | 迪米特法则(最少知识原则)Law of Demeter | 对象只和 “直接朋友” 交互,尽可能少了解其他对象内部 | 尽量不要 a.b.c.doSomething ();减少耦合 | 业务代码层层访问对象内部成员,链式调用深入多层 | 外观模式、中介者模式 |
| CRP | 合成复用原则组合优于继承 | 优先使用对象组合 / 聚合实现复用,谨慎使用类继承 | 继承是静态强耦合;组合运行时可灵活替换依赖 | 疯狂多层继承实现功能扩展,引发类爆炸 | 装饰器、桥接、策略模式 |
| DRY | Don’t Repeat Yourself | 不要重复自己 | 重复逻辑抽取公共模块,杜绝复制粘贴代码 | 多处一模一样的校验、计算代码分散在各个函数 | 通用底层思想,不限特定模式 |
| KISS | Keep It Simple, Stupid | 保持简单 | 能用简单方案就不要引入复杂抽象 | 简单需求强行上多层架构、各种设计模式 | 通用指导思想,避免过度设计 |
| YAGNI | You Aren’t Gonna Need It | 你并不需要它 | 不为未来假想需求提前做设计 | 预估以后会扩展,提前搭建大量抽象,结果需求从未到来 | 通用指导思想,对抗过度设计 |
设计模式总的来说有23种:
| 分类 | 模式名称 | 核心作用 | 典型适用场景 | 关键特点 |
|---|---|---|---|---|
| 创建型(5 种)负责对象实例化,隔离创建与使用 | 单例模式 Singleton | 保证一个类仅有一个实例,提供全局访问点 | 配置管理器、日志管理器、连接池 | 控制实例数量,注意多线程安全 |
| 工厂方法 Factory Method | 定义创建对象接口,由子类决定实例化哪个类 | 同类产品有多种实现,需要扩展产品 | 一个工厂对应一种产品,遵循开闭原则 | |
| 抽象工厂 Abstract Factory | 创建一系列相关 / 相互依赖的对象家族 | 多套配套产品(GUI 跨平台控件:Windows 按钮 / Mac 按钮) | 生产产品族,不单一产品 | |
| 建造者 Builder | 分步构造复杂对象,分离构建与表示 | 对象构造步骤固定,但内部表示多样(组装电脑、报文构造) | 相同构建流程,产出不同成品 | |
| 原型 Prototype | 通过克隆已有对象创建新实例 | 对象创建成本高;大量相似对象 | 基于副本复制,无需重新 new | |
| 结构型(7 种)组装类 / 对象形成更大结构,灵活组合 | 适配器 Adapter | 将一个接口转换成客户端期望的另一个接口 | 兼容旧代码、第三方 SDK 接口不匹配 | 分为类适配器(继承)、对象适配器(组合) |
| 桥接 Bridge | 分离抽象与实现,两者可独立扩展 | 存在两个变化维度(形状 + 颜色、文件 + 格式) | 用组合代替多层继承,消灭类爆炸 | |
| 组合 Composite | 统一对待单个对象和对象容器(树形结构) | 文件目录、菜单、树节点、UI 容器 | 叶子节点与容器对外接口一致 | |
| 装饰器 Decorator | 动态给对象增加职责,不修改原有代码 | IO 流、动态叠加技能 / 特效 | 多层嵌套包装,优于继承扩展 | |
| 外观 Facade | 提供统一高层接口,简化复杂子系统调用 | 复杂底层模块封装,对外暴露简易入口 | 降低调用方复杂度,解耦 | |
| 享元 Flyweight | 复用大量细粒度重复对象,节省内存 | 文字编辑器字符、游戏大量同类型粒子 | 区分内部状态 (共享)、外部状态 (外部传入) | |
| 代理 Proxy | 控制对目标对象的访问,增加额外逻辑 | 远程代理、延迟加载、权限校验、缓存 | 和适配器区别:接口保持一致;适配器做接口转换 | |
| 行为型(11 种)管控对象间通信、算法、职责分配 | 责任链 Chain of Responsibility | 请求沿着一条处理链依次传递,直到被处理 | 审批流、过滤器、日志级别处理 | 请求发送者与接收者解耦 |
| 命令 Command | 将请求封装为对象,支持撤销、排队、日志 | 菜单按钮、宏命令、事务操作、撤销功能 | 请求本身变成可存储对象 | |
| 迭代器 Iterator | 顺序遍历容器元素,不暴露容器内部结构 | List、Tree 自定义遍历器 | 分离容器和遍历逻辑 | |
| 中介者 Mediator | 使用中介对象封装一组对象交互,消除多对多依赖 | 聊天室、UI 组件联动 | 多个对象不再互相持有引用,统一找中介 | |
| 备忘录 Memento | 在不破坏封装前提下保存对象快照,支持恢复 | 编辑器撤销、游戏存档 | 外部不能访问对象内部状态 | |
| 观察者 Observer | 一对多订阅通知,状态变化自动通知所有订阅者 | 事件总线、消息订阅、GUI 事件 | 发布 - 订阅基础模型 | |
| 状态 State | 对象行为随内部状态改变,消除大量 if/switch | 订单状态、游戏角色状态(待机 / 攻击 / 眩晕) | 状态独立成类,状态切换驱动行为变化 | |
| 策略 Strategy | 定义一系列可互换算法,运行时动态替换 | 排序算法、支付方式、不同压缩算法 | 消除算法分支判断 | |
| 模板方法 Template Method | 父类定义算法骨架,子类实现可变步骤 | 框架固定流程,业务自定义部分逻辑 | 继承实现,固定流程,扩展可变节点 | |
| 访问者 Visitor | 在不修改类前提下,新增作用于对象的操作 | 语法树遍历、多类型元素执行多种运算 | 双分派;适合稳定的数据结构 | |
| 解释器 Interpreter | 定义语言文法,构建解释器解析表达式 | 正则表达式、简单脚本、规则引擎 | 使用较少,适合简单小型语法规则 |
下面我来分别用简单的代码示例展示其用途:
创建型模式
单例模式 Singleton
单例模式保证一个类全局仅有唯一实例,并提供统一访问入口,实现上私有化构造函数、使用静态局部实例实现懒加载,禁用拷贝防止实例复制,在游戏开发中常用于全局资源管理器、配置表管理器、输入管理器、音效管理器、游戏全局事件中心、玩家数据管理器,这类模块整个游戏运行期间只需要一份实例。
#include <iostream>
#include <string>
using namespace std;
class AudioManager {
protected:
AudioManager() = default;
public:
static AudioManager& GetInstance() {
static AudioManager instance;
return instance;
}
void PlaySound(const string& name) {
cout << "播放音效:" << name << endl;
}
AudioManager(const AudioManager&) = delete;
AudioManager& operator=(const AudioManager&) = delete;
};
int main() {
AudioManager::GetInstance().PlaySound("attack.wav");
return 0;
}
工厂方法 Factory Method
工厂方法定义创建对象的抽象接口,将实例化具体产品的逻辑延迟到子类,每种产品对应专属工厂,新增对象种类只需要扩展产品类和工厂类,游戏里典型用途是怪物生成器、不同子弹类型创建、技能实体生成、道具实例生产,方便热更新扩展怪物、道具种类。
#include <iostream>
using namespace std;
class Monster {
public:
virtual ~Monster() = default;
virtual void Show() = 0;
};
class Goblin : public Monster {
public:
void Show() override { cout << "生成哥布林怪物" << endl; }
};
class Slime : public Monster {
public:
void Show() override { cout << "生成史莱姆怪物" << endl; }
};
class MonsterFactory {
public:
virtual ~MonsterFactory() = default;
virtual Monster* CreateMonster() = 0;
};
class GoblinFactory : public MonsterFactory {
public:
Monster* CreateMonster() override { return new Goblin(); }
};
class SlimeFactory : public MonsterFactory {
public:
Monster* CreateMonster() override { return new Slime(); }
};
int main() {
MonsterFactory* factory = new GoblinFactory();
Monster* m = factory->CreateMonster();
m->Show();
delete m;
delete factory;
return 0;
}
抽象工厂 Abstract Factory
抽象工厂用于创建一组相互配套、属于同一个产品族的对象,保证成套资源统一创建,实现上抽象工厂定义整套对象创建接口,具体工厂产出完整一套相关实体,游戏开发中适用于不同阵营套装(人族装备 / 魔族装备)、不同风格 UI 套件、不同主题特效组、不同关卡配套怪物 + 道具组合。
#include <iostream>
using namespace std;
class Weapon {
public:
virtual ~Weapon() = default;
virtual void ShowWeapon() = 0;
};
class Armor {
public:
virtual ~Armor() = default;
virtual void ShowArmor() = 0;
};
class HumanSword : public Weapon {
public:
void ShowWeapon() override { cout << "人族长剑" << endl; }
};
class HumanArmor : public Armor {
public:
void ShowArmor() override { cout << "人族板甲" << endl; }
};
class RaceFactory {
public:
virtual ~RaceFactory() = default;
virtual Weapon* CreateWeapon() = 0;
virtual Armor* CreateArmor() = 0;
};
class HumanFactory : public RaceFactory {
public:
Weapon* CreateWeapon() override { return new HumanSword(); }
Armor* CreateArmor() override { return new HumanArmor(); }
};
int main() {
RaceFactory* factory = new HumanFactory();
Weapon* w = factory->CreateWeapon();
Armor* a = factory->CreateArmor();
w->ShowWeapon();
a->ShowArmor();
delete w; delete a; delete factory;
return 0;
}
工厂方法与抽象工厂均依托虚函数将对象创建逻辑交由子类实现,核心差异在于生产范围:工厂方法的抽象工厂仅定义单一产品的创建接口,每个具体工厂只负责生成一类产品,适合单一品类下扩展不同实例;抽象工厂在顶层定义一组多个创建接口,要求具体工厂产出相互配套的整套产品族,以此保证产品之间的搭配一致性,新增产品种类时需要修改顶层抽象接口,维护成本更高,二者可以简单概括为一厂一物和一厂一套。
建造者模式 Builder
建造者将复杂对象的构建流程与对象最终表现分离,相同构造流程可以生成不同配置的实例,依靠建造者分步组装属性、指挥者统一调度构造流程,游戏中适合构造复杂角色(属性、外观、技能、装备分步拼装)、构建技能效果、组装战斗单位、生成复杂关卡数据。
#include <iostream>
#include <string>
using namespace std;
class Hero {
public:
string weapon;
string armor;
void ShowInfo() {
cout << "武器:" << weapon << ",护甲:" << armor << endl;
}
};
class HeroBuilder {
public:
virtual ~HeroBuilder() = default;
virtual void BuildWeapon() = 0;
virtual void BuildArmor() = 0;
virtual Hero GetResult() = 0;
};
class WarriorBuilder : public HeroBuilder {
Hero hero;
public:
void BuildWeapon() override { hero.weapon = "重剑"; }
void BuildArmor() override { hero.armor = "重甲"; }
Hero GetResult() override { return hero; }
};
class Director {
public:
Hero Construct(HeroBuilder& builder) {
builder.BuildWeapon();
builder.BuildArmor();
return builder.GetResult();
}
};
int main() {
Director dir;
WarriorBuilder builder;
Hero h = dir.Construct(builder);
h.ShowInfo();
return 0;
}
工厂模式侧重直接批量生成多个完整、相似的对象,调用后直接得到成品,不关注对象内部构造细节;建造者面向单个结构复杂的对象,把构建过程拆解成多步独立组装工序,在统一构建流程下灵活替换各个组成部件,自由调整对象内部组成与属性配置,用来打造差异化的复杂实例,而非单纯批量产出大量同类对象。
原型模式 Prototype
原型模式通过复制已有对象创建新实例,避开开销巨大的初始化、资源加载流程,基类提供 Clone 克隆接口,子类实现拷贝逻辑,游戏里高频使用在对象池:大量怪物、子弹、粒子、投射物频繁生成销毁,直接克隆原型模板,省去反复加载资源耗时。
#include <iostream>
using namespace std;
class Bullet {
public:
int damage;
Bullet(int dmg) : damage(dmg) {}
virtual ~Bullet() = default;
virtual Bullet* Clone() = 0;
void PrintInfo() { cout << "子弹伤害:" << damage << endl; }
};
class FireBullet : public Bullet {
public:
FireBullet(int dmg) : Bullet(dmg) {}
Bullet* Clone() override {
return new FireBullet(*this);
}
};
int main() {
Bullet* templateBullet = new FireBullet(50);
Bullet* bullet1 = templateBullet->Clone();
bullet1->PrintInfo();
delete templateBullet;
delete bullet1;
return 0;
}
对象池,无需多言,没有人不接触的原型模式。
结构型模式
适配器模式 Adapter
适配器转换现有类的接口,让接口不兼容的模块能够互通,采用组合方式包裹原有对象,对外暴露统一接口,游戏开发常用于对接第三方物理 SDK、兼容新旧两套特效接口、统一不同来源资源加载接口、适配老版本技能数据结构。
#include <iostream>
using namespace std;
// 游戏期望统一技能接口
class ISkill {
public:
virtual ~ISkill() = default;
virtual void Cast() { cout << "释放技能" << endl; }
};
// 旧版特效类,接口不匹配
class OldEffect {
public:
void OldPlay() { cout << "旧版特效播放" << endl; }
};
// 适配器包装旧特效
class SkillAdapter : public ISkill {
OldEffect* effect;
public:
SkillAdapter(OldEffect* e) : effect(e) {}
void Cast() override {
effect->OldPlay();
}
};
int main() {
OldEffect oldEf;
ISkill* skill = new SkillAdapter(&oldEf);
skill->Cast();
delete skill;
return 0;
}
适配器本质就是一段胶水代码,用来抹平两套互不兼容的接口,对外提供统一调用入口,不需要改动原有旧模块的源码,就能让新旧组件、第三方 SDK 顺利协同工作。
桥接模式 Bridge
桥接模式将两个独立变化维度拆分为抽象层与实现层,使用组合代替多层继承避免类爆炸,两个维度可以独立扩展,游戏典型场景:角色 + 技能、技能本体 + 不同特效、单位类型 + 移动方式、物体形状 + 材质。
#include <iostream>
using namespace std;
// 实现层:移动方式
class MoveMode {
public:
virtual ~MoveMode() = default;
virtual void Move() = 0;
};
class Walk : public MoveMode {
public:
void Move() override { cout << "步行移动" << endl; }
};
class Fly : public MoveMode {
public:
void Move() override { cout << "飞行移动" << endl; }
};
// 抽象层:游戏单位
class Unit {
protected:
MoveMode* move;
public:
Unit(MoveMode* m) : move(m) {}
virtual ~Unit() = default;
virtual void Action() = 0;
};
class MonsterUnit : public Unit {
public:
MonsterUnit(MoveMode* m) : Unit(m) {}
void Action() override {
cout << "怪物开始行动:";
move->Move();
}
};
int main() {
MoveMode* fly = new Fly();
Unit* monster = new MonsterUnit(fly);
monster->Action();
delete monster;
delete fly;
return 0;
}
假设游戏单位有怪物、玩家(第一层:抽象维度);移动方式有步行、飞行、传送(第二层:实现维度)。 如果只用继承实现:怪物步行、怪物飞行、玩家步行、玩家飞行…… 后续新增单位或者新增移动方式,子类会疯狂增多。
桥接的做法:把【单位类型】和【移动方式】拆开,移动方式单独做成一套类(实现层),单位内部持有一个移动方式对象,运行时传入任意移动实现。新增一种移动方式,不需要修改任何单位代码;新增单位类型,也不用新增一堆移动相关子类。抽象层负责对外业务接口,实现层负责底层能力,两者依靠组合连接,这道组合关系就是 “桥”。
组合模式 Composite
组合模式构建树形层级结构,统一处理叶子节点和容器节点,客户端不需要区分单个实体和实体集合,游戏大量用于 UI 层级节点、技能效果树、buff 树形结构、场景物体分组、任务目录结构、技能节点编辑器。
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class GameObject {
public:
string name;
GameObject(string n) : name(n) {}
virtual ~GameObject() = default;
virtual void Show(int depth = 0) = 0;
virtual void Add(GameObject*) {}
};
// 叶子:独立物体
class Actor : public GameObject {
public:
Actor(string n) : GameObject(n) {}
void Show(int depth) override {
cout << string(depth, '-') << name << " [角色实体]" << endl;
}
};
// 容器:物体组
class Group : public GameObject {
vector<GameObject*> children;
public:
Group(string n) : GameObject(n) {}
void Add(GameObject* obj) override { children.push_back(obj); }
void Show(int depth = 0) override {
cout << string(depth, '-') << name << " [物体分组]" << endl;
for (auto c : children) c->Show(depth + 1);
}
};
int main() {
Group sceneGroup("场景根节点");
Actor* player = new Actor("玩家");
Group* monsterGroup = new Group("怪物组");
monsterGroup->Add(new Actor("史莱姆"));
sceneGroup.Add(player);
sceneGroup.Add(monsterGroup);
sceneGroup.Show();
return 0;
}
装饰器模式 Decorator
装饰器可以动态叠加功能到对象上,无需修改原始类代码,灵活增删特性,游戏最经典场景:动态叠加 Buff、装备带来属性加成、技能附加特效、攻击叠加元素伤害,运行时自由组合多种效果。
#include <iostream>
using namespace std;
class Attack {
public:
virtual ~Attack() = default;
virtual int GetDamage() = 0;
};
class BaseAttack : public Attack {
public:
int GetDamage() override { return 100; }
};
class AttackDecorator : public Attack {
protected:
Attack* innerAtk;
public:
AttackDecorator(Attack* a) : innerAtk(a) {}
};
class FireBuff : public AttackDecorator {
public:
FireBuff(Attack* a) : AttackDecorator(a) {}
int GetDamage() override {
return innerAtk->GetDamage() + 30;
}
};
int main() {
Attack* atk = new BaseAttack();
atk = new FireBuff(atk);
cout << "总攻击力:" << atk->GetDamage() << endl;
delete atk;
return 0;
}
装饰器模式遵循开闭原则,无需修改原有业务代码,通过对象包装的方式动态扩展对象功能,支持灵活组合多项附加能力,多用于给实体叠加持续性、可自由搭配的效果。
外观模式 Facade
外观模式为一堆复杂底层子系统提供简洁高层接口,隔离外部与多个底层模块的耦合,游戏开发常用:游戏启动流程管理器、战斗系统总入口、资源加载门面,外部只调用简单接口,不用管理资源、渲染、物理、音效多个底层模块协同。
#include <iostream>
using namespace std;
class RenderSystem {
public:
void Init() { cout << "渲染系统初始化" << endl; }
};
class PhysicsSystem {
public:
void Init() { cout << "物理系统初始化" << endl; }
};
// 游戏启动门面
class GameStartupFacade {
RenderSystem render;
PhysicsSystem physics;
public:
void StartGame() {
render.Init();
physics.Init();
cout << "游戏启动完成" << endl;
}
};
int main() {
GameStartupFacade game;
game.StartGame();
return 0;
}
享元模式 Flyweight
享元模式区分对象内部不变共享数据与外部动态数据,复用大量细粒度对象减少内存占用,游戏典型场景:地图瓦片、大量相同粒子模板、字体字符、重复 UI 图标、大量同类型怪物共享静态配置,只在外部传入坐标、血量等动态数据。
#include <iostream>
#include <unordered_map>
using namespace std;
// 瓦片(内部共享:瓦片资源ID)
class Tile {
int resId;
public:
Tile(int id) : resId(id) {}
void Draw(int x, int y) {
cout << "瓦片资源" << resId << " 绘制坐标(" << x << "," << y << ")\n";
}
};
class TileFactory {
unordered_map<int, Tile*> pool;
public:
Tile* GetTile(int resId) {
if (!pool.count(resId)) {
pool[resId] = new Tile(resId);
}
return pool[resId];
}
};
int main() {
TileFactory factory;
Tile* tile1 = factory.GetTile(1001);
Tile* tile2 = factory.GetTile(1001);
cout << boolalpha << (tile1 == tile2) << endl;
tile1->Draw(0, 0);
tile2->Draw(2, 3);
return 0;
}
代理模式 Proxy
代理对象控制对真实对象的访问,可以增加延迟加载、权限校验、缓存逻辑,和装饰器侧重点不同,代理重在访问管控,游戏场景:资源异步懒加载代理、远程角色数据代理、技能冷却拦截代理、贴图延迟加载。
#include <iostream>
using namespace std;
class ITexture {
public:
virtual ~ITexture() = default;
virtual void Render() = 0;
};
class RealTexture : public ITexture {
public:
void Render() override { cout << "渲染真实贴图资源" << endl; }
};
// 贴图代理,延迟加载
class TextureProxy : public ITexture {
RealTexture* tex = nullptr;
public:
void Render() override {
cout << "代理:检查资源,需要时加载贴图" << endl;
if (!tex) tex = new RealTexture();
tex->Render();
}
};
int main() {
ITexture* tex = new TextureProxy();
tex->Render();
delete tex;
return 0;
}
行为型模式
责任链模式 Chain of Responsibility
将一系列处理者串联成链条,请求沿着链条依次传递,每个处理器自行判断是否处理请求、是否向后继续传递,请求发起方无需知晓最终由哪个对象完成处理;适合拆分层层校验、分段处理逻辑,避免大量嵌套 if 分支。游戏场景:伤害计算链路(护甲减免→护盾抵消→吸血判定→暴击结算)、BUFF 依次生效校验、客户端输入指令层层过滤、弹窗优先级依次尝试弹出。
#include <iostream>
using namespace std;
// 上下文:整条链路共享的数据
struct DamageContext
{
int damage;
int armor;
int times;
int level;
};
// 抽象处理器
class BaseHandler
{
public:
BaseHandler* nextHandler = nullptr;
virtual ~BaseHandler() = default;
// 设置下一环
void SetNext(BaseHandler* next)
{
nextHandler = next;
}
virtual void Process(DamageContext& ctx)
{
// 如果存在下一个处理器,继续传递
if (nextHandler != nullptr)
{
nextHandler->Process(ctx);
}
}
};
// 护甲减免处理器
class ArmorHandler : public BaseHandler
{
public:
void Process(DamageContext& ctx) override
{
if (ctx.armor != 0)
{
ctx.damage -= ctx.armor;
}
// 执行父类逻辑:向后传递
BaseHandler::Process(ctx);
}
};
// 倍率处理器
class TimesHandler : public BaseHandler
{
public:
void Process(DamageContext& ctx) override
{
if (ctx.times != 0)
{
ctx.damage *= ctx.times;
}
BaseHandler::Process(ctx);
}
};
// 等级系数处理器
class LevelHandler : public BaseHandler
{
public:
void Process(DamageContext& ctx) override
{
if (ctx.level > 0)
{
ctx.damage *= ctx.level;
}
BaseHandler::Process(ctx);
}
};
int main()
{
// 组装链条:护甲 -> 倍率 -> 等级
ArmorHandler armor;
TimesHandler times;
LevelHandler level;
armor.SetNext(×);
times.SetNext(&level);
DamageContext ctx{100, 20, 2, 3};
armor.Process(ctx);
cout << "最终伤害:" << ctx.damage << endl;
return 0;
}
对于一段由多个处理环节组成的请求处理流程,希望做到环节解耦、动态拼装顺序,并且允许任意环节按需终止后续流程,避免臃肿的硬编码分支,这个时候往往就需要责任链模式了。
命令模式 Command
命令模式将动作请求封装为独立对象,隔离请求发起者与动作实际执行者;命令具备统一执行接口,可以被保存、排队、延时触发,额外天然支持撤销、回放逻辑。游戏场景:玩家操作撤销系统、战斗指令回放、技能队列缓冲、遥控器式按键绑定、AI 行为指令封装。
#include <iostream>
#include <vector>
using namespace std;
// ====================== 接收者 Receiver ======================
class Player
{
public:
int x = 0, y = 0;
void Move(int nx, int ny)
{
x = nx;
y = ny;
cout << "角色移动至 (" << x << "," << y << ")\n";
}
void Attack()
{
cout << "角色发起攻击\n";
}
};
// ====================== 抽象命令 Command ======================
class BaseCommand
{
public:
virtual ~BaseCommand() = default;
virtual void Execute() = 0;
virtual void Undo() = 0;
};
// ====================== 具体命令 ======================
class MoveCommand : public BaseCommand
{
public:
Player* player;
int targetX, targetY;
int oldX, oldY;
MoveCommand(Player* p, int tx, int ty)
: player(p), targetX(tx), targetY(ty)
{}
void Execute() override
{
oldX = player->x;
oldY = player->y;
player->Move(targetX, targetY);
}
void Undo() override
{
player->Move(oldX, oldY);
cout << "撤销移动,回到 (" << oldX << "," << oldY << ")\n";
}
};
class AttackCommand : public BaseCommand
{
public:
Player* player;
AttackCommand(Player* p) : player(p)
{}
void Execute() override
{
player->Attack();
}
void Undo() override
{
cout << "撤销本次攻击(清除伤害判定)\n";
}
};
// ====================== 调用者 Invoker ======================
class InputInvoker
{
private:
vector<BaseCommand*> history;
public:
void ExecuteCommand(BaseCommand* cmd)
{
cmd->Execute();
history.push_back(cmd);
}
void Undo()
{
if (history.empty())
{
cout << "没有可撤销操作\n";
return;
}
BaseCommand* last = history.back();
history.pop_back();
last->Undo();
}
// 内存释放
void Clear()
{
for (auto cmd : history)
{
delete cmd;
}
history.clear();
}
};
// ====================== 客户端测试 ======================
int main()
{
Player hero;
InputInvoker invoker;
cout << "=====执行操作=====\n";
invoker.ExecuteCommand(new MoveCommand(&hero, 5, 5));
invoker.ExecuteCommand(new AttackCommand(&hero));
cout << "\n=====触发撤销=====\n";
invoker.Undo();
invoker.Undo();
invoker.Clear();
return 0;
}
迭代器模式 Iterator
迭代器模式提供一套统一接口遍历容器中的元素,对外隐藏容器底层存储结构(数组、链表、树、空间分区结构等),外部代码只依赖迭代器接口访问元素,不直接操作容器内部数据。游戏场景:遍历背包道具、场景怪物列表、技能目标检索、导航网格节点遍历、资源清单统一遍历。
struct Monster { int hp; };
class BaseIterator
{
public:
virtual bool HasNext() = 0;
virtual Monster* Next() = 0;
};
// 独立迭代器类,不内嵌
class MonsterVecIterator : public BaseIterator
{
private:
MonsterManager* mgr;
int index;
public:
MonsterVecIterator(MonsterManager* p) : mgr(p), index(0){}
bool HasNext() override;
Monster* Next() override;
};
class MonsterManager
{
private:
vector<Monster*> monsters;
public:
BaseIterator* CreateIterator()
{
return new MonsterVecIterator(this);
}
};
中介者模式 Mediator
中介者模式引入一个中心化中介对象,解除多个业务对象之间网状互相依赖;所有交互对象不再直接持有对方引用,消息全部发送给中介,由中介负责转发分发;新增交互对象时,不需要修改其余参与者代码。游戏场景:多人房间玩家消息同步、各个 UI 窗口解耦通信、战斗单位事件分发、联机大厅消息转发、技能全局事件中心。
// 抽象同事
class BaseColleague
{
public:
class Mediator* mediator = nullptr;
virtual void ReceiveMsg(const string& msg) = 0;
};
// 抽象中介(可选,简单案例可以先不用抽象中介,直接写具体中介)
class Mediator
{
private:
vector<BaseColleague*> members; // 保存全部通信对象
public:
// 注册对象
void Register(BaseColleague* c)
{
members.push_back(c);
}
// 发送消息:只传消息+发送者,由中介分发
void SendMsg(const string& msg, BaseColleague* sender)
{
for(auto c : members)
{
if(c != sender) // 不发给自己
{
c->ReceiveMsg(msg);
}
}
}
};
class Bag : public BaseColleague
{
public:
// 背包发生变化,向外发消息
void OnEquipChange()
{
mediator->SendMsg("装备发生变更", this);
}
void ReceiveMsg(const string& msg) override
{
// 处理收到的消息
}
};
class Character : public BaseColleague
{
public:
void ReceiveMsg(const string& msg) override
{
if(msg == "装备发生变更")
{
// 刷新角色属性面板
}
}
};
所有参与通信的对象继承统一同事基类,基类持有中介指针;各个具体业务对象(背包、角色面板)自身不认识其他对象,产生事件时,只把消息和自身引用一并交给中介;中介维护一份所有参与者清单,收到消息后遍历、路由、推送给其余对象。
备忘录模式 Memento
备忘录模式在不暴露对象内部私有数据、不破坏封装的前提下,捕获对象当前全部状态生成快照,后续可以随时将原对象恢复至快照状态;由原发器创建快照,负责人只负责存储快照,无权读取快照内部数据。
// 前置声明
class Memento;
// ===================== 原发器 Originator:角色 =====================
class Character
{
private:
int hp;
int mp;
int x, y;
public:
void SetInfo(int h, int m, int px, int py)
{
hp = h;
mp = m;
x = px;
y = py;
}
void ShowInfo() const
{
cout << "hp:" << hp << " mp:" << mp << " 坐标(" << x << "," << y << ")\n";
}
// 创建快照
Memento* CreateMemento();
// 使用快照恢复
void Restore(Memento* m);
};
// ===================== 备忘录 Memento =====================
class Memento
{
private:
int hp;
int mp;
int x, y;
// 友元:仅允许Character访问私有成员,保护封装
friend class Character;
public:
Memento(int hp_, int mp_, int x_, int y_)
: hp(hp_), mp(mp_), x(x_), y(y_)
{}
};
Memento* Character::CreateMemento()
{
return new Memento(hp, mp, x, y);
}
void Character::Restore(Memento* m)
{
hp = m->hp;
mp = m->mp;
x = m->x;
y = m->y;
}
// ===================== 负责人 Caretaker:快照管理器 =====================
class BattleCaretaker
{
private:
vector<Memento*> snapshotList;
public:
void SaveSnapshot(Memento* m)
{
snapshotList.push_back(m);
}
Memento* GetLastSnapshot()
{
if (snapshotList.empty())
return nullptr;
return snapshotList.back();
}
// 释放所有快照内存
void ClearAll()
{
for (auto ptr : snapshotList)
{
delete ptr;
}
snapshotList.clear();
}
};
备忘录就是单独创建快照类,把原发器某一刻所有状态完整保存,后续支持恢复。核心难点是封装保护:快照的数据不能随便被外部篡改,但原发器又必须能够读写快照内部状态。C++ 场景下采用友元是很经典的实现方案,只开放权限给原发器,隔绝其他所有类。
观察者模式 Observer
观察者模式定义一对多的依赖关系:当目标对象(被观察者)状态发生变化时,所有依赖它的观察者会自动收到通知并执行更新逻辑。被观察者不需要知道观察者的具体类型,只认识统一的观察者基类。
#include <vector>
//抽象观察者
class BaseObserver
{
public:
virtual ~BaseObserver() = default;
virtual void Update(int hp) = 0;
};
//抽象被观察者
class BaseSubject
{
protected:
std::vector<BaseObserver*> obsList;
public:
virtual ~BaseSubject() = default;
void Attach(BaseObserver* o){ obsList.push_back(o); }
void Detach(BaseObserver* o){ /* 移除逻辑 */ }
void Notify(int hp)
{
for(auto o : obsList) o->Update(hp);
}
};
//被观察者:玩家
class Player : public BaseSubject
{
int hp;
public:
void SetHp(int val)
{
hp = val;
Notify(hp);
}
};
//具体观察者
class HpUI : public BaseObserver
{
public:
void Update(int hp) override
{
//刷新血条
}
};
被观察者维护观察者列表;数据改变调用 Notify,统一触发所有观察者的 Update。
状态模式 State
状态模式允许对象在内部状态改变时改变它的行为,对象看起来如同修改了自身所属类;把每种状态的逻辑拆分到独立状态类,消除大量 if-else 分支。游戏场景:角色状态机(待机、奔跑、跳跃、攻击、受击、死亡)、技能冷却状态、AI 行为状态。
//前置声明
class PlayerContext;
//抽象状态
class BaseState
{
public:
virtual ~BaseState() = default;
virtual void Enter(PlayerContext* ctx) = 0;
virtual void Update(PlayerContext* ctx) = 0;
virtual void Exit(PlayerContext* ctx) = 0;
};
//上下文(角色)
class PlayerContext
{
private:
BaseState* curState;
public:
void SetState(BaseState* s){
if(curState) curState->Exit(this);
curState = s;
curState->Enter(this);
}
void Update(){
if(curState) curState->Update(this);
}
};
//具体状态:待机
class IdleState : public BaseState
{
public:
void Enter(PlayerContext* ctx) override{}
void Update(PlayerContext* ctx) override{
//检测输入,满足条件切换奔跑/攻击状态
}
void Exit(PlayerContext* ctx) override{}
};
//具体状态:奔跑
class RunState : public BaseState
{
public:
void Enter(PlayerContext* ctx) override{}
void Update(PlayerContext* ctx) override{}
void Exit(PlayerContext* ctx) override{}
};
所有状态互不依赖,新增「跳跃状态」只需新建类,不用修改原有代码;状态之间不直接互相 new 对方,全部通过上下文 Context 完成状态切换;优势:消灭超长 if/else 判断;缺点:状态数量会导致类爆炸。
策略模式 Strategy
策略模式把一系列可互换的算法 / 行为封装成独立策略类,拥有统一抽象接口;上下文持有抽象策略,运行时动态替换策略,消除大量 if-else。 游戏场景:怪物 AI 攻击方式切换(近战 / 远程)、技能释放算法、寻路方案切换、角色不同移动模式。
//抽象策略
class BaseStrategy
{
public:
virtual ~BaseStrategy() = default;
virtual void Execute() = 0;
};
//具体策略1:近战攻击
class MeleeAttack : public BaseStrategy
{
public:
void Execute() override
{
//近战逻辑
}
};
//具体策略2:远程攻击
class RangedAttack : public BaseStrategy
{
public:
void Execute() override
{
//远程逻辑
}
};
//上下文:怪物
class Monster
{
private:
BaseStrategy* attackStrategy;
public:
void SetStrategy(BaseStrategy* s)
{
attackStrategy = s;
}
void Attack()
{
if(attackStrategy)
attackStrategy->Execute();
}
};
所有策略实现同一个基类接口,可以互相替换;怪物(上下文)只认识抽象策略,不关心具体是近战还是远程;运行时调用 SetStrategy 动态更换行为。
模板方法模式
父类定义固定的执行流程骨架,把流程中可变步骤定义为虚函数,交由子类实现;整体执行顺序永远不变。依靠继承实现复用。
class BaseSkill
{
public:
//模板方法,固定流程,禁止重写
void CastSkill()
{
CheckCondition();
PreCast();
Execute();
AfterCast();
}
protected:
void CheckCondition(){} //通用固定逻辑
virtual void PreCast(){} //可选重写
virtual void Execute() = 0; //必须子类实现
void AfterCast(){}
};
class FireSkill : public BaseSkill
{
protected:
void Execute() override
{
//火球伤害逻辑
}
};
访问者模式 Visitor
在不修改各个元素类代码的前提下,新增一套作用于元素的外部操作;把「数据结构」和「数据操作」分离。 适合场景:一组固定结构的对象,需要频繁新增不同算法 / 行为。游戏例子:怪物、玩家、NPC(元素);可以新增【血量打印访问者】、【伤害结算访问者】、【BUFF 刷新访问者】,不用改动角色类本身。
//前置声明访问者
class BaseVisitor;
//抽象元素
class BaseActor
{
public:
virtual ~BaseActor() = default;
virtual void Accept(BaseVisitor* visitor) = 0;
};
//抽象访问者
class BaseVisitor
{
public:
virtual ~BaseVisitor() = default;
virtual void VisitPlayer(class Player* actor) = 0;
virtual void VisitMonster(class Monster* actor) = 0;
};
//具体元素:玩家
class Player : public BaseActor
{
public:
void Accept(BaseVisitor* v) override
{
v->VisitPlayer(this);
}
};
//具体元素:怪物
class Monster : public BaseActor
{
public:
void Accept(BaseVisitor* v) override
{
v->VisitMonster(this);
}
};
//具体访问者:伤害计算
class DamageVisitor : public BaseVisitor
{
public:
void VisitPlayer(Player* actor) override
{
//玩家受伤逻辑
}
void VisitMonster(Monster* actor) override
{
//怪物受伤逻辑
}
};
访问者 = 一套外部操作容器,可以遍历多种不同实体;依靠双分派消除类型判断 if-else;代价是元素类型很难扩展。
更多推荐
所有评论(0)