今天学习一些设计模式的内容,这个其实也没有啥可参考的内容,主要就是问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
单例模式保证一个类全局仅有唯一实例,并提供统一访问入口,实现上私有化构造函数、使用静态局部实例实现懒加载,禁用拷贝防止实例复制,在游戏开发中常用于全局资源管理器、配置表管理器、输入管理器、音效管理器、游戏全局事件中心、玩家数据管理器,这类模块整个游戏运行期间只需要一份实例。
cpp
#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
工厂方法定义创建对象的抽象接口,将实例化具体产品的逻辑延迟到子类,每种产品对应专属工厂,新增对象种类只需要扩展产品类和工厂类,游戏里典型用途是怪物生成器、不同子弹类型创建、技能实体生成、道具实例生产,方便热更新扩展怪物、道具种类。
cpp
#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 套件、不同主题特效组、不同关卡配套怪物 + 道具组合。
cpp
#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
建造者将复杂对象的构建流程与对象最终表现分离,相同构造流程可以生成不同配置的实例,依靠建造者分步组装属性、指挥者统一调度构造流程,游戏中适合构造复杂角色(属性、外观、技能、装备分步拼装)、构建技能效果、组装战斗单位、生成复杂关卡数据。
cpp
#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 克隆接口,子类实现拷贝逻辑,游戏里高频使用在对象池:大量怪物、子弹、粒子、投射物频繁生成销毁,直接克隆原型模板,省去反复加载资源耗时。
cpp
#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、兼容新旧两套特效接口、统一不同来源资源加载接口、适配老版本技能数据结构。
cpp
#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
桥接模式将两个独立变化维度拆分为抽象层与实现层,使用组合代替多层继承避免类爆炸,两个维度可以独立扩展,游戏典型场景:角色 + 技能、技能本体 + 不同特效、单位类型 + 移动方式、物体形状 + 材质。
cpp
#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 树形结构、场景物体分组、任务目录结构、技能节点编辑器。
cpp
#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、装备带来属性加成、技能附加特效、攻击叠加元素伤害,运行时自由组合多种效果。
cpp
#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
外观模式为一堆复杂底层子系统提供简洁高层接口,隔离外部与多个底层模块的耦合,游戏开发常用:游戏启动流程管理器、战斗系统总入口、资源加载门面,外部只调用简单接口,不用管理资源、渲染、物理、音效多个底层模块协同。
cpp
#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 图标、大量同类型怪物共享静态配置,只在外部传入坐标、血量等动态数据。
cpp
#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
代理对象控制对真实对象的访问,可以增加延迟加载、权限校验、缓存逻辑,和装饰器侧重点不同,代理重在访问管控,游戏场景:资源异步懒加载代理、远程角色数据代理、技能冷却拦截代理、贴图延迟加载。
cpp
#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;
}