今天学习一些设计模式的内容,这个其实也没有啥可参考的内容,主要就是问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合成复用原则组合优于继承优先使用对象组合 / 聚合实现复用,谨慎使用类继承继承是静态强耦合;组合运行时可灵活替换依赖疯狂多层继承实现功能扩展,引发类爆炸装饰器、桥接、策略模式
DRYDon’t Repeat Yourself不要重复自己重复逻辑抽取公共模块,杜绝复制粘贴代码多处一模一样的校验、计算代码分散在各个函数通用底层思想,不限特定模式
KISSKeep It Simple, Stupid保持简单能用简单方案就不要引入复杂抽象简单需求强行上多层架构、各种设计模式通用指导思想,避免过度设计
YAGNIYou 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);
    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;代价是元素类型很难扩展。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐