当前位置: 首页 > news >正文

设计模式:代码界的 “光之巨人” 养成指南(附 C++ 实战)

参考

https://bbs.huaweicloud.com/blogs/397606
https://refactoring.guru/design-patterns/catalog

一、概述

1.1、什么是设计模式

官方定义说得有点绕:“一套被反复使用、多数人知晓、分类编目的代码设计经验总结”。其实翻译成人话就是:前人踩过的坑,总结成的 “避坑指南”。

为啥要用设计模式?简单说就是让代码 “三好学生”:

  • 可重用:不用每次写功能都从零开始,像奥特曼不用每次变身都重新造变身器;

  • 易理解:别人看你代码时,不用 “猜谜”—— 哦,这是单例模式,那是工厂模式,秒懂;

  • 够可靠:减少 “牵一发而动全身” 的 bug,毕竟没人想改一行代码,整个系统就 “光之熄灭”。

值得提一嘴:设计模式本身是 “无语言差别的编程理念”,但落到 C++ 上,就成了 “用类和对象玩出花” 的技巧 —— 比如用虚函数实现多态,用继承拆分职责。

咱这篇本质是个人学习复盘笔记,主打一个自说自话,要是讲得有点绕,还请各位先握好 C++ 面向对象,不然容易翻车。

1.2、设计模式分类

设计模式总共分三类,不用现在死记硬背,先混个脸熟就行,后面有机会的话每个都会拉出来 “单独唠”,就像逛超市先看货架分区,不用立刻背完所有商品名:

类别 核心作用 包含模式(共 23 种)
创建型模式 管 “对象怎么造” 单例、工厂、抽象工厂、建造者、原型(5 种)
结构型模式 管 “类 / 对象怎么拼” 代理、装饰者、适配器、桥接、组合、外观、享元(7 种)
行为型模式 管 “类 / 对象怎么互动、分职责” 模板、命令、责任链、策略、中介者、观察者、备忘录、访问者、状态、解释器、迭代器(11 种)

记住:分类是为了 “好记”,不是为了 “画三八线”—— 实际写代码时,模式混搭也很常见,就像奥特曼有时会同时用力量和速度技能。

1.3、设计模式的六大准则

设计模式的核心是 “抗变化”:毕竟需求总在变(产品经理:这个功能再改一版),要是代码一变就崩,那跟没变身器的奥特曼没区别。

这六大准则就是 “抗变化” 的核心

1.3.1 开放封闭原则:“能加新功能,别改老代码”

对扩展开放,对更改封闭。类模块应该是可扩展的,但是不可修改。

核心:对扩展开放,对修改封闭。简单说就是:新增需求时,优先 “加代码”,别 “改 existing 代码”—— 毕竟老代码可能已经跑了半年,改坏了就是 “牵一发而动全身”,搞不好还得背锅。

举个例子:给迪迦做变身功能,要是每次加形态都往一个类里塞,就像给变身器硬塞新按钮,哪天按错了可能连 “光之巨人” 都变 “光之废人” 了。

class TigaUltraman1
{
public:void RedForm() //红色形态{cout << "红色形态的迪迦奥特曼" << endl;}void BlueForm() //蓝色形态{cout << "蓝色形态的迪迦奥特曼" << endl;}void CompreForm() //综合形态{cout << "综合形态的迪迦奥特曼" << endl;}
};int main()
{TigaUltraman1* u1 = new TigaUltraman1;u1->RedForm();u1->BlueForm();u1->CompreForm();delete u1;cout << endl;
}

要是想加 “闪耀形态”,就得改这个类 —— 万一不小心删了 “红色形态” 的代码,嘿嘿。

再看正确姿势(遵守原则版):用抽象类 “画红线”,新形态只需要 “加子类”,不碰老代码:

// 第一步:定义抽象类(变身器的“通用插槽”,稳定不变)
class TigaUltraman {
public:virtual void transform() = 0; // 纯虚函数:变身逻辑交给子类virtual ~TigaUltraman() {} // 虚析构:避免子类内存泄漏
};// 第二步:加新形态=加子类(扩展开放)
class RedTiga : public TigaUltraman {
public:void transform() override { cout << "红色形态:重拳砸怪兽!" << endl; }
};
class BlueTiga : public TigaUltraman {
public:void transform() override { cout << "蓝色形态:高速闪避让技能!" << endl; }
};
// 新增闪耀形态:只加代码,不改老的
class GlitterTiga : public TigaUltraman {
public:void transform() override { cout << "闪耀形态:全身发光秒Boss!" << endl; }
};// 第三步:核心逻辑只认抽象类(修改封闭)
void useTigaTransform(TigaUltraman* tiga) {tiga->transform(); // 不管你是啥形态,插进来就能用
}int main() {TigaUltraman* red = new RedTiga();TigaUltraman* glitter = new GlitterTiga(); // 新增形态无压力useTigaTransform(red);    // 输出红色逻辑useTigaTransform(glitter);// 输出闪耀逻辑delete red; delete glitter;return 0;
}

这就是 “开放封闭” 的精髓:老代码(抽象类 + 核心逻辑)不动,新功能(子类)随便加,安全感拉满。

1.3.2 单一职责原则:“一个类只干一件事,别当万金油”

核心:一个类只有一个 “被修改的理由”。就像奥特曼只负责打怪兽,送快递该找顺丰,别让奥特曼又打怪兽又送包裹 —— 哪天怪兽没打赢还丢了快递,你都不知道该骂它 “战斗力不行” 还是 “配送效率低”。

// 违反单一职责:一个类同时负责“奥特曼战斗”和“送快递”两个无关职责
class TigaWithDelivery {
public:// 职责1:打怪兽(战斗相关)void fight(std::string monster) {std::cout << "迪迦打败了" << monster << "!" << std::endl;}// 职责2:送快递(和战斗完全无关)void deliverPackage(std::string address, std::string goods) {std::cout << "迪迦顺便把[" << goods << "]送到了" << address << std::endl;}
};int main() {TigaWithDelivery tiga;tiga.fight("加坦杰厄"); // 打怪兽tiga.deliverPackage("胜利队基地", "紧急补给物资"); // 送快递return 0;
}

TigaWithDelivery 类同时干了两件八竿子打不着的事:

  • 奥特曼的核心职责 —— 打怪兽(fight 方法);
  • 完全无关的职责 —— 送快递(deliverPackage 方法)。

如果要改战斗逻辑(比如给迪迦加 “闪耀形态”),或者改快递规则(比如加 “配送时间限制”),都得修改这个类。就像 “迪迦打怪兽时还得惦记着送快递,结果怪兽没打好,快递也送迟了”—— 两个职责互相干扰,出了问题都不知道该先优化哪一个。

两个毫不相关的需求变化,都要修改同一个类,容易出问题(比如改展示时不小心删了保存的代码)。因次我们需要将职责进行拆分:

// 类1:只负责奥特曼战斗(核心职责)
class TigaFighter {
public:void fight(std::string monster) {std::cout << "迪迦打败了" << monster << ",保护了城市!" << std::endl;}
};// 类2:只负责送快递(独立职责,和奥特曼战斗无关)
class DeliveryService {
public:void deliver(std::string address, std::string goods) {std::cout << "快递已将[" << goods << "]送到" << address << ",收件人请签收!" << std::endl;}
};int main() {TigaFighter tiga;          // 专门打怪兽DeliveryService delivery;  // 专门送快递tiga.fight("美尔巴");      // 战斗交给奥特曼delivery.deliver("东京湾沿岸", "奥特曼能量补充包");  // 送快递交给专门的服务return 0;
}

1.3.3 依赖倒置原则

核心:高层模块(比如 “奥特曼用技能”)别依赖低层模块(比如 “红色形态技能”),两者都依赖抽象(比如 “技能接口”)。就像奥特曼不用管变身器是 “红色按钮” 还是 “蓝色按钮”,只要按 “变身接口” 就能变 —— 依赖具体按钮,哪天按钮坏了就变不了了。

我们用 “奥特曼使用技能” 的场景来说明依赖倒置原则,核心是:高层模块(奥特曼)不依赖具体形态,而是依赖 “形态的抽象”;具体形态(低层)依赖这个抽象。

迪迦奥特曼有多种形态(红色、蓝色),每种形态有不同的技能(红色形态有 “强力冲击”,蓝色形态有 “高速飞踢”)。我们需要设计一个 “迪迦使用技能” 的逻辑。

反例(踩坑版):高层直接绑死具体形态,像给迪迦焊死红色变身器:

// 具体形态1:红色形态(低层模块)
class RedForm {
public:void useSkill() {std::cout << "红色形态使用技能:强力冲击!" << std::endl;}
};// 具体形态2:蓝色形态(低层模块)
class BlueForm {
public:void useSkill() {std::cout << "蓝色形态使用技能:高速飞踢!" << std::endl;}
};// 高层模块:技能释放模块(直接依赖具体形态)
class TigaUltraman {
private:RedForm redForm; // 直接依赖具体的红色形态BlueForm blueForm; // 直接依赖具体的蓝色形态public:// 使用红色形态技能void useRedSkill() {redForm.useSkill();}// 使用蓝色形态技能void useBlueSkill() {blueForm.useSkill();}
};int main() {TigaUltraman tiga;tiga.useRedSkill(); // 用红色技能tiga.useBlueSkill(); // 用蓝色技能return 0;
}

乍一看其实可读性还不错,但是需求变化(比如新增 “闪耀形态”,或修改红色形态的类名),必须修改 TigaUltraman 的代码(比如新增 useGlitterSkill 方法,或修改 RedForm 的引用)。

这就是 “高层依赖低层细节” 的问题:低层一变,高层就得跟着改,耦合太紧,扩展困难。

正确姿势(依赖抽象版):用接口当 “中间层”,高层认接口,低层实现接口:

// 抽象接口:形态的抽象规则(不依赖任何具体形态)
class IMorph {
public:virtual void useSkill() = 0; // 所有形态必须实现“使用技能”virtual ~IMorph() {}
};// 具体形态1:红色形态(低层依赖抽象)
class RedForm : public IMorph {
public:void useSkill() override {std::cout << "红色形态使用技能:强力冲击!" << std::endl;}
};// 具体形态2:蓝色形态(低层依赖抽象)
class BlueForm : public IMorph {
public:void useSkill() override {std::cout << "蓝色形态使用技能:高速飞踢!" << std::endl;}
};// 新增形态:闪耀形态(扩展时只需加新实现,不影响高层)
class GlitterForm : public IMorph {
public:void useSkill() override {std::cout << "闪耀形态使用技能:闪耀光线!" << std::endl;}
};// 高层模块:技能释放(只依赖抽象接口,不依赖具体形态)
class TigaUltraman {
private:IMorph* currentMorph; // 依赖抽象,而非具体类public:// 通过构造函数传入具体形态(依赖注入)TigaUltraman(IMorph* morph) : currentMorph(morph) {}// 切换形态(更换具体实现,高层代码不变)void switchMorph(IMorph* newMorph) {currentMorph = newMorph;}// 使用当前形态的技能(调用抽象方法,具体由低层实现)void useCurrentSkill() {currentMorph->useSkill();}
};int main() {// 1. 初始形态:红色IMorph* red = new RedForm();TigaUltraman tiga(red);tiga.useCurrentSkill(); // 红色技能// 2. 切换到蓝色形态(只需传新的具体实现,高层代码不变)IMorph* blue = new BlueForm();tiga.switchMorph(blue);tiga.useCurrentSkill(); // 蓝色技能// 3. 新增闪耀形态(完全不修改TigaUltraman)IMorph* glitter = new GlitterForm();tiga.switchMorph(glitter);tiga.useCurrentSkill(); // 闪耀技能// 释放资源delete red;delete blue;delete glitter;return 0;
}

这样高层模块 TigaUltraman 只依赖 IMorph 抽象接口,不关心具体是红色、蓝色还是闪耀形态;低层模块(RedForm、BlueForm 等)都依赖 IMorph 接口(实现接口),而不是抽象依赖它们;当新增形态(如闪耀形态)或修改现有形态时,TigaUltraman 的代码完全不用改,只需新增 / 修改具体形态类。

层和低层通过抽象 “解耦”,低层随便换,高层稳如老狗。

1.3.4 接口隔离原则:“别搞万能接口,要搞专用接口”

核心:一个接口只提供一种功能,别让用户被迫依赖不需要的方法。就像给奥特曼的装备包塞了力量拳套、速度跑鞋、光线枪 —— 但红色形态只想要拳套,结果被迫背着跑鞋和枪,不仅沉,还容易不小心按到光线枪打偏。

反例(踩坑版):万能接口塞所有能力:

// 万能接口:塞了力量、速度、光线所有能力
class IUltramanAbility {
public:virtual void strongAttack() = 0; // 力量virtual void fastMove() = 0;     // 速度virtual void lightSkill() = 0;   // 光线virtual ~IUltramanAbility() {}
};// 红色形态:只想要力量,却被迫实现速度和光线(只能空转)
class RedForm : public IUltramanAbility {
public:void strongAttack() override { cout << "红色:强力重拳!" << endl; }// 被迫实现不需要的方法,冗余又误导void fastMove() override { cout << "红色:我跑不快啊!" << endl; }void lightSkill() override { cout << "红色:光线没劲儿!" << endl; }
};

用户用红色形态时,看到 fastMove () 还以为它能跑,结果调用了才知道不行 —— 这就是 “接口污染”。

正确姿势(拆分接口版):一个接口干一件事,需要啥就实现啥:

// 专用接口1:力量型能力(仅包含力量相关方法)
class IStrengthAbility {
public:virtual void strongAttack() = 0;virtual ~IStrengthAbility() {}
};// 专用接口2:速度型能力(仅包含速度相关方法)
class ISpeedAbility {
public:virtual void fastMove() = 0;virtual ~ISpeedAbility() {}
};// 专用接口3:光线型能力(仅包含光线相关方法)
class ILightAbility {
public:virtual void lightSkill() = 0;virtual ~ILightAbility() {}
};// 红色形态:只实现自己需要的“力量能力”
class RedForm : public IStrengthAbility {
public:void strongAttack() override {std::cout << "红色形态:强力重拳!" << std::endl;}
};// 蓝色形态:实现“速度能力”和“光线能力”(它需要这两个)
class BlueForm : public ISpeedAbility, public ILightAbility {
public:void fastMove() override {std::cout << "蓝色形态:高速闪避!" << std::endl;}void lightSkill() override {std::cout << "蓝色形态:哉佩利敖光线!" << std::endl;}
};// 新增:纯光线形态(只需要“光线能力”)
class LightOnlyForm : public ILightAbility {
public:void lightSkill() override {std::cout << "纯光线形态:终极光线!" << std::endl;}
};int main() {IStrengthAbility* red = new RedForm();red->strongAttack(); // 只调用自己需要的能力,无冗余BlueForm* blue = new BlueForm();blue->fastMove();    // 调用需要的速度能力blue->lightSkill();  // 调用需要的光线能力ILightAbility* lightForm = new LightOnlyForm();lightForm->lightSkill(); // 只调用光线能力delete red;delete blue;delete lightForm;return 0;
}

1.3.5 里氏替换原则:“花木兰替父从军”

核心:子类可以替换父类出现的任何地方,并且程序行为不变。简单说就是:子类得 “继承父类的约定”,别搞 “叛逆”—— 比如父类说 “攻击是打怪兽”,子类非要改成 “攻击是喂怪兽”,那替换后程序直接崩了。

反例(踩坑版):子类破坏父类约定:

// 父类:奥特曼(定义核心行为约定)
class Ultraman {
public:// 攻击:约定为“对敌人造成伤害”(输出正数表示伤害值)virtual int attack() {return 50; // 基础伤害}// 防御:约定为“减少自身受到的伤害”(输入敌人攻击值,返回实际承受的伤害)virtual int defend(int enemyAttack) {return enemyAttack - 20; // 减少20点伤害}virtual ~Ultraman() {}
};// 子类:红色形态(错误重写,破坏父类约定)
class RedForm : public Ultraman {
public:// 错误:把“攻击”改成了“治疗敌人”(返回负数,违反“造成伤害”的约定)int attack() override {return -30; // 负数表示治疗敌人,与父类约定冲突}// 错误:把“防御”改成了“增加伤害”(承受伤害比原来更高,违反“减少伤害”的约定)int defend(int enemyAttack) override {return enemyAttack + 10; // 反而多承受10点伤害}
};// 测试函数:使用父类对象,预期攻击造成伤害,防御减少伤害
void fight(Ultraman* ultraman) {int damage = ultraman->attack();std::cout << "攻击效果:" << (damage > 0 ? "造成" : "治疗") << abs(damage) << "点伤害" << std::endl;int enemyAttack = 100;int actualDamage = ultraman->defend(enemyAttack);std::cout << "受到的伤害:" << actualDamage << "(原伤害:" << enemyAttack << ")" << std::endl;
}int main() {Ultraman* normal = new Ultraman();std::cout << "正常奥特曼战斗:" << std::endl;fight(normal); // 符合预期:攻击造成50伤害,防御后承受80伤害std::cout << "\n红色形态战斗(替换父类后):" << std::endl;Ultraman* red = new RedForm();fight(red); // 不符合预期:攻击变成治疗,防御后伤害更高delete normal;delete red;return 0;
}

这就是违反里氏替换的后果:子类替换父类后,程序行为完全不符合预期,跟 “光之巨人变卧底” 似的。

正确的子类应该 “遵守父类约定”:比如红色形态 attack () 返回 100(比父类强,但还是打伤害),defend () 返回 enemyAtk-30(减更多伤害)—— 这样替换后,程序还是 “打怪兽”,只是更强了。

1.3.6 迪米特法则:“只跟朋友聊天,别跟陌生人搭话”

核心:一个类只和 “朋友”(成员变量、方法参数 / 返回值的类)交互,别跟 “陌生人”(方法里临时出现的类)瞎聊。就像你点外卖,只需要跟外卖员(朋友)要餐,不用进厨房跟厨师(陌生人)聊天 —— 厨师怎么做饭是他的事,你不用管。

反例(踩坑版):奥特曼跟 “陌生人”(能量核心)搭话:

// 奥特曼(Ultraman)需要使用武器(Weapon)攻击敌人,武器的攻击力由内部的能量核心(EnergyCore)决定。我们来看看符合和违反第米特法则的两种设计。
// 如果奥特曼为了获取攻击力,先通过武器拿到能量核心,再从能量核心获取攻击力,就相当于和 “能量核心” 这个陌生人直接交互了:// 能量核心(陌生人:奥特曼不该直接访问它)
class EnergyCore {
private:int power; // 核心能量(决定攻击力)
public:EnergyCore(int p) : power(p) {}int getPower() { return power; } // 提供能量值
};// 武器(奥特曼的直接朋友)
class Weapon {
private:EnergyCore core; // 武器包含能量核心
public:Weapon(EnergyCore c) : core(c) {}// 直接暴露内部的能量核心(导致奥特曼可能直接访问)EnergyCore getCore() { return core; }
};// 奥特曼(主角)
class Ultraman {
private:Weapon weapon; // 奥特曼持有武器(直接朋友)
public:Ultraman(Weapon w) : weapon(w) {}// 攻击:为了获取攻击力,先访问武器的能量核心(陌生人)void attack() {// 错误:奥特曼直接访问了“能量核心”(陌生人)EnergyCore core = weapon.getCore(); int damage = core.getPower(); std::cout << "奥特曼攻击,造成" << damage << "点伤害" << std::endl;}
};int main() {EnergyCore core(100);Weapon weapon(core);Ultraman tiga(weapon);tiga.attack();return 0;
}

这么做有什么问题呢?

如果能量核心的实现变化,比如改 getPower() 为 getEnergy(),奥特曼的代码也需要修改,因为它直接调用了 core.getPower();

奥特曼需要知道 “武器包含能量核心”“能量核心有 getPower() 方法” 等细节,耦合度极高,维护成本高。

正确姿势(只跟朋友聊版):让朋友(武器)处理陌生人(能量核心),奥特曼只找武器要结果:

// 能量核心(被武器封装,奥特曼无需知道它)
class EnergyCore {
private:int power;
public:EnergyCore(int p) : power(p) {}int getPower() { return power; }
};// 武器(奥特曼的直接朋友,封装内部细节)
class Weapon {
private:EnergyCore core; // 内部细节,不暴露给外部
public:Weapon(EnergyCore c) : core(c) {}// 提供“获取攻击力”的方法(封装对能量核心的访问)int getDamage() {return core.getPower(); // 武器自己访问能量核心(它的直接朋友)}
};// 奥特曼(只与直接朋友“武器”交流)
class Ultraman {
private:Weapon weapon; // 直接朋友
public:Ultraman(Weapon w) : weapon(w) {}// 攻击:只调用武器的方法,不关心内部细节void attack() {int damage = weapon.getDamage(); // 只和直接朋友(武器)交流std::cout << "奥特曼攻击,造成" << damage << "点伤害" << std::endl;}
};int main() {EnergyCore core(100);Weapon weapon(core);Ultraman tiga(weapon);tiga.attack();return 0;
}

这样,奥特曼(Ultraman)只和直接朋友 “武器(Weapon)” 交互(调用 weapon.getDamage()),完全不知道能量核心(EnergyCore)的存在,武器(Weapon)自己处理与能量核心的交互(能量核心是武器的直接朋友),对外只暴露简单的 getDamage() 方法,即使能量核心的实现变化(比如改 getPower() 为 getEnergy()),只需修改武器的 getDamage() 方法,奥特曼的代码完全不用动。

1.4 总结

设计模式不是 “银弹”,不是说学了就能写出 “完美代码”—— 它更像 “工具箱”,遇到对应问题时能掏出合适的 “工具”。咱这篇先把 “基础概念” 和 “六大准则” 捋顺,后面再逐个拆解 23 种设计模式,用更多奥特曼的例子把每个模式讲透。

要是哪里没看懂,不用慌,设计模式本来就是越用越懂的东西,先混个脸熟,后面写代码时再回头翻,慢慢就悟了。

二、创建型模式

创建型模式,说白了就是跟创建对象"较劲的模式。它们不满足于简单的new一下,而是琢磨着怎么把对象创建搞得更灵活、更靠谱。家族成员有五位:单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。

2.1 单例模式

单例模式的核心执念是:一个类只能有一个实例,而且得有个全局入口能找到它。

为什么需要单例模式?
有些东西天生就该是独苗,多一个就乱套:

  • 系统配置管理器:要是同时跑三个配置实例,A 说 "字体 12 号",B 说 "字体 14 号",界面怕是要精神分裂;
  • 日志管理器:多个日志实例同时写文件,最后日志可能变成 "张三登录... 李四下单... 张三登..." 的乱码剧本;
  • 线程池 / 数据库连接池:重复创建就是纯纯浪费资源,还可能引发 "两个池抢同一个连接" 的血案。

用奥特曼的故事讲更明白:光之国的 "宇宙警备队总指挥部" 只能有一个,所有奥特曼的任务都从这发。要是冒出两个指挥部,一个喊 "打怪兽!",一个喊 "快撤退!",奥特曼怕是得原地劈叉。这时候,单例模式就是最佳指挥官。

2.1.1 懒汉式单例模式

当我们在使用类来new创建一个对象的时候,会自动调用构造函数,每创建一个对象都会调用构造函数来构造一个新的对象

class Ultraman{};void func(){Ultraman* A = new Ultraman;Ultraman* B = new Ultraman;if(A != B){std::cout << "A 和 B 是两个不同的对象";}
}

要实现 “奥特曼总部只有一个”,核心是禁止外部随意创建实例,同时提供 “唯一入口” 获取实例。具体步骤如下:

  • 构造函数私有化:阻止外部通过new调用构造函数;
  • 内部定义静态指针:存储唯一的总部实例(静态成员属于类,所有对象共享);
  • 提供全局访问点:通过静态方法控制实例创建(确保只new一次)。
class UltramanHQ{
private:UltramanHQ(){cout << "奥特曼总部创建!(构造函数调用)" << endl;}static UltramanHQ* single;public://构造函数被私有化了,所以应该提供一个对外访问的方法,来创建对象static UltramanHQ* get_single() {if (single == NULL) //为保证单例,只new一次{					//如果不加这个判断,每次创建对象都会new一个single,这就不是单例了single = new UltramanHQ;}return single; //静态成员属于整个类,没有this指针}
};SingletonPattern* UltramanHQ::single = NULL; int main() {UltramanHQ* s1 = UltramanHQ::get_single(); //在get_single中会new一个对象UltramanHQ* s2 = UltramanHQ::get_single(); if (s1 == s2) cout << "单例" << endl;else cout << "不是单例" << endl; 
}

为啥叫 "懒汉"?因为它太懒了,直到第一次有人调用get_single(),才创建实例,不到万不得已绝不干活,能省点资源是点。上面程序所示的就是一个懒汉式单例模式的实现。这里面有几点要注意的:

(1)为了让这个类所定义的所有对象共享属性,应该把属性设置为static类型,因为static类型的属性属于整个类而不是属于某个对象。

(2)为了保证单例模式,应该在全局访问点get_single()函数中加一个判断,如果对象已经被创建了,那么就直接返回这个对象,如果对象还没有被创建,那么久new创建一个对象,并返回该对象。

(3)因为是在使用到对象的时候,才去创建对象(single初始化为NULL,在全局访问点get_single被调用的时候才去创建对象),有点偷懒的感觉,所以称之为懒汉式单例模式。

但是,懒汉式单例模式有一个致命的缺点,就是在C++的构造函数中,不能保证线程安全。比如三个线程同时调用get_single(),第一个线程刚判断完single==NULL,正准备new,结果被卡了1秒(比如构造函数里有延迟)。这时候第二个、第三个线程也来判断,发现single还是NULL,于是都跑去new,最后造出三个总部,单例直接变 "三例"。

首先,我们把类改造一下,在构造函数中加一个延时,并在类中加一个计数器来记录构造函数的调用次数

#include <iostream>
#include <string>
#include <thread>       // C++11 跨平台线程库
#include <chrono>       // 跨平台时间单位
using namespace std;class UltramanHQ{
private:UltramanHQ(){this_thread::sleep_for(chrono::seconds(1));count++;cout << "奥特曼总部创建!(构造函数调用)" << endl;}static UltramanHQ* single;static int count;// 统计构造函数调用次数public:static int getCount() { return count; }static UltramanHQ* get_single(){if (single == NULL) single = new UltramanHQ;     // 多线程可能同时进入这里return single; }
};UltramanHQ* UltramanHQ::single = NULL; 
int UltramanHQ::count = 0;

这样,一个类就定义好了。接下来,我们要创建三个线程,每个线程都去创建一个对象

// 线程函数定义
void threadFunc() {UltramanHQ::get_single();
}void testLazyMultiThread() {const int THREAD_NUM = 3;  // 3个线程同时请求thread threads[THREAD_NUM]; // 创建3个线程for (int i = 0; i < THREAD_NUM; i++) {threads[i] = thread(threadFunc);}// 等待所有线程结束for (int i = 0; i < THREAD_NUM; i++) {if (threads[i].joinable()) {  // 检查线程是否可 jointhreads[i].join();}}cout << "最终构造函数调用次数 = " << UltramanHQ::getCount() << endl;
}

运行测试函数,得到以下结果:

奥特曼总部创建!(构造函数调用)
奥特曼总部创建!(构造函数调用)
奥特曼总部创建!(构造函数调用)
最终构造函数调用次数 = 3

可以看到,构造函数调用了三次,每个线程都创建了一个新的对象,已经不再是单例模式了。对于这个问题的解决主要有两种,下面分别介绍。

2.1.2 DCL与饿汉式

针对多线程问题,有两种常见解决方案:饿汉式、DCL(双重检查锁定)。

(1) 饿汉式单例模式

饿汉式的逻辑是提前把饭做好,程序一启动就把实例建好,管它用不用,先占着坑。这样多线程再抢也没用,实例早就有了。

饿汉式代码实现很简单,只需修改静态指针的初始化方式,从null改为直接new:

class UltramanHQ{
private:UltramanHQ(){this_thread::sleep_for(chrono::seconds(1));count++;cout << "奥特曼总部创建!(构造函数调用)" << endl;}static UltramanHQ* single;static int count;// 统计构造函数调用次数public:static int getCount() { return count; }static UltramanHQ* get_single(){ // 无需判断,实例早已有了return single; }
};// 饿汉式关键:静态指针初始化时直接new实例(程序启动时执行)
UltramanHQ* UltramanHQ::single = new UltramanHQ; 
// UltramanHQ* UltramanHQ::single = NULL; 
int UltramanHQ::count = 0;

再次运行看打印结果:

奥特曼总部创建!(构造函数调用)
最终构造函数调用次数 = 1

可以看出,三个不同的线程只调用了一次类的构造函数,得到的是同一个对象。饿汉式的优势在于简单粗暴,天生线程安全,但缺点是万一这个实例一直不用,就白占资源了。

(2) DCL(双重检查锁定)

DCL 的核心是 “加锁 + 双重判断”:既保持懒汉式" 用的时候再创建 " 的优点,又避免饿汉式的资源浪费(用的时候再创建)。

既然多个线程会竞争资源,那么我们对临界区资源加一个锁,当一个线程持有锁的时候,其他线程挂起等待锁的释放,只有持有锁的线程才能进入临界资源,这就解决了多线程资源竞争的问题。这里还有一个问题,当我们第一次判断(single == NULL)后,如果之前没有创建对象,那么就进入下面的临界区

if (single == NULL){mtx.lock();     // 手动加锁single = new UltramanHQ;     // 多线程可能同时进入这里mtx.unlock();   // 手动解锁(必须执行,否则死锁)
} 

当第一个线程创建完对象后释放了锁,第二个线程进入临界区又创建了一个对象,这也违反了单例原则。所以应该加入一个二次检查,如果第一个线程已经创建了对象(指针不为NULL),那么第二个线程即使获取了锁,也不再创建新的对象,而是直接使用第一个线程创建的对象,这就是二次检测的原因。

static UltramanHQ* get_single(){if (single == NULL){mtx.lock();     // 手动加锁if (single == NULL) single = new UltramanHQ;     // 多线程可能同时进入这里mtx.unlock();   // 手动解锁(必须执行,否则死锁)} return single; 
}

2.1.3 总结

单例模式主要有懒汉式和饿汉式两种实现,饿汉式不会有线程安全的问题,但是提前构造对象占用了一定的资源,如果对内存要求较低的场景可以使用饿汉式实现;懒汉式应使用DCL机制来避免多线程竞争资源的问题,并且懒汉式可以在需要使用对象的时候才去创建对象,节省了资源。

类型 特点 适合场景
懒汉式 用的时候才创建,省资源但线程不安全(需 DCL 修复) 资源紧张,实例不常用
饿汉式 启动就创建,线程安全但可能浪费资源 资源充足,实例一定会用到

2.2 工厂模式

工厂模式的核心思想:把对象创建的活儿交给专门的 "工厂",客户端不用自己new,直接找工厂要。这样一来,创建逻辑集中管理,改起来方便(比如换个奥特曼型号,改工厂就行,不用改所有客户端)。

简单工厂模式

简单工厂就像个 "万能车间":你说要啥奥特曼,它就给你造啥。

用奥特曼的场景来理解:光之国需要“生产”不同的奥特曼(如迪迦、赛罗、泽塔),如果每次需要奥特曼都直接“new”一个,会导致创建逻辑散落在代码各处,新增奥特曼时修改成本很高。这时候就需要一个“奥特曼工厂”,统一负责创建各种奥特曼 —— 这就是工厂模式的核心。

// 抽象奥特曼基类(
class Ultraman{
public:virtual void fight()=0;// 纯虚函数:战斗(每个奥特曼的战斗方式不同)virtual ~Ultraman(){}
};
// 具体奥特曼子类
// 迪迦奥特曼
class Tiga: public Ultraman{
public:void fight() override{cout << "迪迦使用哉佩利敖光线!" << endl;}
};
// 赛罗奥特曼
class Zero: public Ultraman{
public:void fight() override{cout << "赛罗使用赛罗头镖!" << endl;}
};//简单工厂类(负责创建所有奥特曼)
class UltramanFactory{
public:Ultraman* createUltraman(const string& name){if (name == "迪迦") return new Tiga();else if (name == "赛罗") return new Zero();else{cout << "工厂暂时无法生产该奥特曼!" << endl;return nullptr;}}
};

核心优点:工厂类是整个简单工厂模式的核心,通过工厂类对外隐藏了创建实例的具体细节,用户直接使用工厂类去创建自己需要的实例,而不必关心实例是如何创建出来的,也不必关心内部结构是怎么组织的。

缺点:简单工厂模式的优点来源于工厂类,其缺点也来源于工厂类,因为所有实例的创建逻辑都集中在工厂类中,一旦工厂出现问题,所有实例的创建都将无法进行,并且增删产品都要去修改工厂类来实现,不符合开闭原则,所以简单工厂不算标准设计模式,更像个简易版过渡方案。、。

工厂模式

将简单工厂的单一工厂拆分为抽象工厂基类 + 多个具体工厂子类,每个具体工厂只负责创建一种具体对象,也称为多态工厂模式。工厂模式对简单工厂模式不遵守开闭原则这一缺点做了修正,工厂模式多出了一个抽象工厂角色作为接口,实际的生产工作在具体工厂类中实现,这样进一步的抽象化使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。简单来说,就是把简单工厂中的工厂细分为不同产品的工厂,每个工厂生产一种产品。

  • 抽象工厂角色(Creator),所有具体工厂都要实现这个接口;
    
  • 具体工厂(Concrete Creator),负责实例化具体产品对象;
    
  • 抽象角色(Product),和简单工厂模式一样,它是工厂类所创建的所有实例的类的共同基类,用于描述产品的公共接口。
    
  • 具体产品角色(Concrete Product),具体工厂类所要实例化的对象。
    

场景:光之国为每种奥特曼设立专门的工厂(迪迦工厂只造迪迦,赛罗工厂只造赛罗),再通过抽象工厂基类统一管理。

抽象奥特曼基类和具体奥特曼类不变:

// 抽象奥特曼基类(
class Ultraman{
public:virtual void fight()=0;// 纯虚函数:战斗(每个奥特曼的战斗方式不同)virtual ~Ultraman(){}
};
// 具体奥特曼子类
// 迪迦奥特曼
class Tiga: public Ultraman{
public:void fight() override{cout << "迪迦使用哉佩利敖光线!" << endl;}
};
// 赛罗奥特曼
class Zero: public Ultraman{
public:void fight() override{cout << "赛罗使用赛罗头镖!" << endl;}
};

定义一个抽象的工厂类,来定义具体工厂的统一接口:

class IUltramanFactory {
public:virtual Ultraman* create() = 0; // 纯虚函数:创建奥特曼virtual ~IUltramanFactory() {}
};

然后定义具体工厂子类,那么这两个工厂就是具体工厂角色:

// 4. 具体工厂子类(每个工厂只造一种奥特曼)
// 迪迦工厂
class TigaFactory : public IUltramanFactory {
public:Ultraman* create() override {return new Tiga(); // 只造迪迦}
};// 赛罗工厂
class ZeroFactory : public IUltramanFactory {
public:Ultraman* create() override {return new Zero(); // 只造赛罗}
};
// 客户端:通过具体工厂获取奥特曼
int main() {// 创建迪迦工厂,生产迪迦IUltramanFactory* tigaFactory = new TigaFactory();Ultraman* tiga = tigaFactory->create();tiga->fight();// 创建赛罗工厂,生产赛罗IUltramanFactory* zeroFactory = new ZeroFactory();Ultraman* zero = zeroFactory->create();zero->fight();return 0;
}

核心优点:符合开放封闭原则:新增奥特曼时,只需新增类,无需修改现有工厂代码;每个工厂职责单一(只造一种奥特曼),符合单一职责原则。

缺点:每新增一个奥特曼,就需要新增一个对应的工厂类,类的数量会成对增加(如果奥特曼类型很多,可能导致类膨胀)。

2.3 抽象工厂模式

如果光之国要生产基础版和终极版两套系列奥特曼(基础版迪迦 + 基础版赛罗;终极版迪迦 + 终极版赛罗),用工厂模式得建 4 个工厂(基础迪迦、基础赛罗、终极迪迦、终极赛罗),这时候,就有了抽象工厂模式,抽象工厂模式可以创建一个产品族(包含多条产品线)。抽象工厂模式用官方语言描述就是,一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。她有四种角色,和工厂模式一样。

  • 抽象工厂角色(Creator),所有具体工厂都要实现这个接口,可以创建多个不同等级的产品(多条产品线);
    
  • 具体工厂(Concrete Creator),负责实例化具体产品对象,多条产品线;
    
  • 抽象角色(Product),和简单工厂模式一样,它是工厂类所创建的所有实例的类的共同基类,用于描述产品的公共接口。
    
  • 具体产品角色(Concrete Product),具体工厂类所要实例化的对象。
    

首先创建一个抽象产品类

// 1. 抽象产品:奥特曼(产品系列的顶层接口)
class Ultraman {
public:virtual void useSkill() = 0; // 技能方法(不同变体实现不同)virtual ~Ultraman() = default;
};

根据抽象产品类定义具体产品类及变体:

// 基础版迪迦
class BasicTiga : public Ultraman {
public:void useSkill() override {cout << "基础版迪迦:使用普通哉佩利敖光线(无特效)" << endl;}
};// 基础版赛罗
class BasicZero : public Ultraman {
public:void useSkill() override {cout << "基础版赛罗:使用普通头镖切割(无特效)" << endl;}
};// 终极版迪迦
class UltimateTiga : public Ultraman {
public:void useSkill() override {cout << "终极版迪迦:使用闪耀哉佩利敖光线(金色光效,威力×2)" << endl;}
};// 终极版赛罗
class UltimateZero : public Ultraman {
public:void useSkill() override {cout << "终极版赛罗:使用终极头镖(彩色光效,威力×2)" << endl;}
};

接下来定义一个抽象工厂类,该抽象工厂类中包含两个接口,一个是基础版生产线,一个是终极版生产线

class IUltraFactory {
public:virtual Ultraman* createTiga() = 0;   // 创建迪迦(具体变体由子类决定)virtual Ultraman* createZero() = 0;   // 创建赛罗(具体变体由子类决定)virtual ~IUltraFactory() = default;
};

接下来定义两个具体工厂,一个是基础版工厂,一个终极版工厂

// 基础版工厂:生产基础版迪迦+基础版赛罗(风格统一:普通技能)
class BasicFactory : public IUltraFactory {
public:Ultraman* createTiga() override {return new BasicTiga(); // 基础版迪迦}Ultraman* createZero() override {return new BasicZero(); // 基础版赛罗}
};// 终极版工厂:生产终极版迪迦+终极版赛罗(风格统一:强化技能+光效)
class UltimateFactory : public IUltraFactory {
public:Ultraman* createTiga() override {return new UltimateTiga(); // 终极版迪迦}Ultraman* createZero() override {return new UltimateZero(); // 终极版赛罗}
};

客户可以直接使用两个具体工厂去生产

int main() {// 场景1:使用基础版变体IUltraFactory* basicFactory = new BasicFactory();Ultraman* basicTiga = basicFactory->createTiga();Ultraman* basicZero = basicFactory->createZero();cout << "=== 基础版奥特曼技能展示 ===" << endl;basicTiga->useSkill();basicZero->useSkill();// 场景2:使用终极版变体IUltraFactory* ultimateFactory = new UltimateFactory();Ultraman* ultimateTiga = ultimateFactory->createTiga();Ultraman* ultimateZero = ultimateFactory->createZero();cout << "\n=== 终极版奥特曼技能展示 ===" << endl;ultimateTiga->useSkill();ultimateZero->useSkill();// 释放资源delete basicTiga;delete basicZero;delete basicFactory;delete ultimateTiga;delete ultimateZero;delete ultimateFactory;return 0;
}

抽象工厂类总结

当增加一个新的产品族时,只需要增加一个新的具体工厂即可,如果整个产品族只有一个等级的产品,比如只有一条生产线,抽象工厂就和工厂模式一样了。

2.4 建造者模式

建造者模式,也叫做生成器模式,是一种对象创建型模式。建造者模式用于创建具有多个部件的复合对象,并隐藏了复合对象的创建过程,不同的部件建造者(Builder生成器)有不同的建造方法。通过建造者模式实现了对象的构建和对象的表示的分离,也就是说,通过同样的构建过程(建造逻辑)可以创建出不同的表示(使用不同的建造者产生不同的建造方式)。

简单说就是:有些对象特别复杂,由多个部件组成(比如奥特曼的 "形态":颜色、技能、武器都得配齐)。如果直接硬编码创建逻辑,会搞得一团糟。因此把怎么拼部件和最终拼成啥样分开。让建造者负责拼细节,指挥者负责控步骤,灵活生成不同产品。

奥特曼的形态很复杂(比如 “复合型迪迦”“空中型迪迦”“强力型迪迦”),每种形态都由颜色、核心技能、专属武器三个部分组成,但具体细节不同:

  • 复合型:红紫配色、哉佩利敖光线、无武器;
  • 空中型:蓝紫配色、兰帕尔特光弹、光剑;
  • 强力型:红银配色、迪拉休姆光流、重拳。

如果直接在代码中硬编码每种形态的创建,会导致逻辑混乱(创建步骤和形态细节混在一起)。建造者模式的解决思路是:

  • 让 “建造者” 专注于每种形态的组件细节(比如空中型的颜色怎么设、技能是什么);
  • 让 “指挥者” 专注于固定的构建步骤(先定颜色→再设技能→最后加武器);
  • 想创建新形态时,只需新增一个建造者,无需修改构建步骤。

核心角色:

  • 产品(Product):复杂对象(如奥特曼形态);
  • 抽象建造者(Abstract Builder):定义拼部件的接口(设颜色、设技能、设武器);
  • 具体建造者(Concrete Builder):实现接口,拼具体形态(如空中型建造者);
  • 指挥者(Director):控制步骤(先设颜色→再设技能→最后加武器)。

首先我们定义一个产品类,这个奥特曼由颜色、核心技能、专属武器三个部分组成,并且产品类中应该包含设置各个部件和获取各个部件的方法。

lass UltramanForm {
private:string color;    // 颜色string skill;    // 核心技能string weapon;   // 专属武器public:// 设置各个组件void setColor(string c) { color = c; }void setSkill(string s) { skill = s; }void setWeapon(string w) { weapon = w; }// 展示形态信息void show() {cout << "奥特曼形态:" << endl;cout << "颜色:" << color << endl;cout << "核心技能:" << skill << endl;cout << "专属武器:" << weapon << endl << endl;}
};

定义一个抽象的建造者基类,类中统一了建造部件的接口和返回产品成品的方法。

class UltramanBuilder {
public:// 纯虚函数:构建各个组件(颜色、技能、武器)virtual void buildColor() = 0;virtual void buildSkill() = 0;virtual void buildWeapon() = 0;// 获取构建好的产品virtual UltramanForm* getForm() = 0;virtual ~UltramanBuilder() = default;
};

定义具体建造者类,具体建造者类是产品部件的具体建造者,我们构建空中型和强力型两种建造者

// 空中型建造者(蓝紫配色、速度型技能)
class SkyFormBuilder : public UltramanBuilder {
private:UltramanForm* form; // 持有产品对象public:SkyFormBuilder() {form = new UltramanForm(); // 初始化产品}// 构建空中型的颜色void buildColor() override {form->setColor("蓝紫色");}// 构建空中型的技能void buildSkill() override {form->setSkill("兰帕尔特光弹(速度型)");}// 构建空中型的武器void buildWeapon() override {form->setWeapon("光剑");}// 返回构建好的空中型UltramanForm* getForm() override {return form;}
};// 强力型建造者(红银配色、力量型技能)
class PowerFormBuilder : public UltramanBuilder {
private:UltramanForm* form;public:PowerFormBuilder() {form = new UltramanForm();}void buildColor() override {form->setColor("红银色");}void buildSkill() override {form->setSkill("迪拉休姆光流(力量型)");}void buildWeapon() override {form->setWeapon("重拳");}UltramanForm* getForm() override {return form;}
};

最后,定义一个指挥者,控制构建流程,按固定步骤调用建造者

class Director {
public:// 指挥建造流程:先颜色→再技能→最后武器(步骤固定)void construct(UltramanBuilder* builder) {builder->buildColor();   // 第一步:构建颜色builder->buildSkill();   // 第二步:构建技能builder->buildWeapon();  // 第三步:构建武器}
};

最后是根据客户需求去建造

int main() {Director director; // 创建指挥者(负责流程)// 场景1:创建空中型奥特曼UltramanBuilder* skyBuilder = new SkyFormBuilder();director.construct(skyBuilder); // 指挥者按步骤构建UltramanForm* skyForm = skyBuilder->getForm();skyForm->show();// 场景2:创建强力型奥特曼UltramanBuilder* powerBuilder = new PowerFormBuilder();director.construct(powerBuilder); // 同样的流程,不同的建造者UltramanForm* powerForm = powerBuilder->getForm();powerForm->show();return 0;
}

2.5 原型模式

如果你有一个对象, 并希望生成与其完全相同的一个复制品, 你该如何实现呢? 首先, 你必须新建一个属于相同类的对象。 然后, 你必须遍历原始对象的所有成员变量, 并将成员变量值复制到新对象中。

但有个小问题。 并非所有对象都能通过这种方式进行复制, 因为有些对象可能拥有私有成员变量, 它们在对象本身以外是不可见的。

直接复制还有另外一个问题。 因为你必须知道对象所属的类才能创建复制品, 所以代码必须依赖该类。 即使你可以接受额外的依赖性, 那还有另外一个问题: 有时你只知道对象所实现的接口, 而不知道其所属的具体类, 比如可向方法的某个参数传入实现了某个接口的任何对象。

解决方案

原型模式的核心:用克隆代替新建,让对象自己会复制自己,客户端直接调用clone()就能拿到复制品,还不用知道创建细节。

用 “奥特曼复制” 场景理解

光之国需要派 10 个迪迦去不同地区巡逻,每个迪迦的颜色、技能都一样,只有 "部署地区" 不同。如果每个都new Tiga("红紫", "光线", "东京")再改地区,重复代码太多。

用原型模式:先造一个 "迪迦原型",需要新迪迦时直接克隆原型,再改地区就行。

// 抽象原型
class UltramanPrototype {
public:virtual UltramanPrototype* clone() = 0; // 克隆方法(核心)virtual void show() = 0;                // 展示信息virtual void setArea(string area) = 0;  // 允许修改地区(差异属性)virtual ~UltramanPrototype() = default;
};// 具体原型
class Tiga : public UltramanPrototype {
private:string color;   // 固定属性:颜色string skill;   // 固定属性:技能string area;    // 可变属性:部署地区(克隆后可修改)public:// 构造函数:初始化原型的基础属性(创建原型时执行一次)Tiga(string c, string s, string a) : color(c), skill(s), area(a) {}// 核心:实现克隆方法(复制当前对象的所有属性)UltramanPrototype* clone() override {// 创建一个新的Tiga,复制当前对象的属性(浅克隆)return new Tiga(color, skill, area); }// 修改部署地区(克隆后的差异调整)void setArea(string area) override {this->area = area;}// 展示信息void show() override {cout << "迪迦信息:" << endl;cout << "颜色:" << color << endl;cout << "技能:" << skill << endl;cout << "部署地区:" << area << endl << endl;}
};// 客户端:通过克隆原型创建新对象
int main() {// 1. 创建原型:基础版迪迦(只需要创建一次)UltramanPrototype* tigaPrototype = new Tiga("红紫色(复合型)", "哉佩利敖光线", "东京"  // 原型的初始地区);cout << "=== 原型迪迦 ===" << endl;tigaPrototype->show();// 2. 克隆原型,创建新迪迦(无需重新设置颜色和技能)UltramanPrototype* tiga2 = tigaPrototype->clone();tiga2->setArea("上海");cout << "=== 克隆迪迦(上海) ===" << endl;tiga2->show();// 释放资源delete tigaPrototype;delete tiga2;return 0;
}

但是这里还有一个问题,既然涉及到类的拷贝,就会有深浅拷贝的问题,上面的例子是浅克隆:只复制对象本身的基本属性(如color、skill等值类型),如果对象包含指针/引用类型的成员(比如奥特曼有一个Weapon*武器指针),浅克隆只会复制指针地址,新老对象共享同一份武器数据,这就可能导致意外修改。

如果需要完全独立的复制(连指针指向的内容也复制),则需要深拷贝:

// 假设迪迦有一个武器指针(需要深克隆)
class Weapon {
public:string name;Weapon(string n) : name(n) {}
};class Tiga : public UltramanPrototype {
private:Weapon* weapon; // 指针成员(需要深克隆)public:// 构造函数Tiga(Weapon* w) : weapon(new Weapon(*w)) {}// 深克隆:不仅复制指针,还复制指针指向的内容UltramanPrototype* clone() override {return new Tiga(new Weapon(*weapon)); // 复制Weapon对象}// ...其他方法
};

2.6 创建型设计模式总结

顾名思义,创建型设计模式就是处理对象创建过程的设计模式。创建型模式本质上都是 "对象创建的套路",核心是隐藏创建细节,让客户端更省心。创建型设计模式主要包括:

模式 核心技能
单例模式 确保对象独一份,提供全局访问点
工厂模式 用工厂统一造对象,分简单工厂和多态工厂
抽象工厂模式 工厂造一整套相关产品(产品族)
建造者模式 分步构建复杂对象,步骤和细节分离
原型模式 克隆已有对象快速创建复制品

简单工厂因为不符合开闭原则,常被视为工厂模式的 "简化版"。选择哪种模式?看场景:要独一份用单例,要批量生产用工厂 / 抽象工厂,要复杂对象分步造用建造者,要快速复制用原型。

三、创建型模式

如果说创建型模式是教你怎么造零件,那结构型模式就是教你怎么搭积木,如何把零散的对象和类组装成灵活又高效的大结构,同时还能保证这堆积木好拆好改。

3.1 适配器模式

适配器模式是结构型模式里的“万能转换器”,核心思想特简单:当两个组件的接口对不上,就用一个适配器当翻译,把A的调用转成B能懂的格式,让原本鸡同鸭讲的两者能合作干活。

说通俗点,这就像你出国旅游带的电源适配器,酒店插座是220V圆孔,你手机充电器是5V扁口,适配器一插,问题解决。

用奥特曼武器适配场景理解:光之国存在新老两种声控武器接口:

  • 老款武器(如 “初代光线枪”):只有oldFire()方法(旧接口),发射时会喊 “初代光线!”;
  • 新款奥特曼(如 “泽塔奥特曼”):只能调用newAttack()方法(新接口),期望武器响应 “泽塔指令:攻击!”。

此时老武器和新奥特曼接口不兼容(泽塔喊 “newAttack”,老武器听不懂),需要一个 “武器适配器”,把newAttack()的调用翻译成oldFire(),让泽塔能用老武器。

适配器模式的核心角色

  • 目标接口:客户端期望的接口(如泽塔需要的newAttack());
  • 适配者:需要被适配的旧接口(如老武器的oldFire());
  • 适配器:实现目标接口,内部包装适配者,将目标接口的调用转换为适配者的接口调用(翻译官)。
// 1. 目标接口(Target):客户端(新奥特曼)期望的接口
class NewWeapon {
public:virtual void newAttack() = 0; // 新接口:泽塔只认这个方法virtual ~NewWeapon() = default;
};// 2. 适配者(Adaptee):需要被适配的旧接口(老武器)
class OldRayGun {
public:// 旧接口:老武器只实现这个方法void oldFire() {cout << "初代光线枪:发射初代光线!(旧接口响应)" << endl;}
};// 3. 适配器(Adapter):连接新接口和旧接口
class WeaponAdapter : public NewWeapon { // 实现目标接口(新接口)
private:OldRayGun* oldGun; // 内部包装适配者(老武器)public:// 构造时传入老武器WeaponAdapter(OldRayGun* gun) : oldGun(gun) {}// 核心:将新接口调用转换为旧接口调用(翻译过程)void newAttack() override {cout << "适配器收到:泽塔指令→攻击!(开始翻译)" << endl;oldGun->oldFire(); // 调用老武器的旧方法}
};// 客户端:新款奥特曼(泽塔),只认识新接口
class Zett {
private:NewWeapon* weapon; // 只接受NewWeapon接口public:Zett(NewWeapon* w) : weapon(w) {}// 泽塔攻击时,只调用newAttack()void fight() {cout << "泽塔:准备攻击!" << endl;weapon->newAttack(); // 调用新接口}
};// 测试:泽塔通过适配器使用老武器
int main() {// 有一把老武器(旧接口)OldRayGun* oldGun = new OldRayGun();// 创建适配器,包装老武器(让老武器“看起来像”新接口)NewWeapon* adapter = new WeaponAdapter(oldGun);// 泽塔拿着适配器(以为是新武器)Zett zett(adapter);// 泽塔攻击:调用新接口,实际由老武器响应zett.fight();// 释放资源delete adapter;delete oldGun;return 0;
}

3.2 代理模式

代理模式就是给某个对象找个替身,让替身控制对原对象的访问,还能在调用前后加塞一些额外操作(比如检查、记录、权限控制)。

用奥特曼战斗代理场景理解:

光之国规定:奥特曼出击前必须先检查怪兽等级,避免低等级怪兽浪费战力,战斗后必须记录战果以方便统计。但奥特曼的核心职责是战斗,不想被这些额外操作干扰。此时可以用代理来处理这些辅助工作:

  • 真实对象:奥特曼(只负责战斗);
  • 代理对象:奥特曼助手(负责出击前检查、出击后记录,再调用奥特曼的战斗方法);
  • 客户端:基地(只和代理沟通,无需知道奥特曼的具体处理细节)。

对应了在代理模式中的三种角色:

  • 抽象主题角色:真实主题与代理主题的共同接口,并供客户端使用;
  • 真实主题角色: 定义了代理角色所代表的真实实体。
  • 代理主题角色:持有真实对象的引用,能在调用前后加操作。
// 抽象主题
class Fighter{
public:virtual void fight(string monster) = 0;virtual ~Fighter() = default;
};// 真实对象
class Ultraman : public Fighter{
private:string name;
public:Ultraman(string n) : name(n){}void fight(string monster) override{cout << name << "击败了" << monster << endl;}
};
// 代理
class UltramanProxy : public Fighter{
private:Fighter* realUltraman;// 代理的额外操作1:出击前检查怪兽等级void checkMonsterLevel(string monster) {cout << "【代理】检查怪兽等级:" << monster << "是B级,允许出击!" << endl;}// 代理的额外操作2:战斗后记录战果void recordResult(string monster) {cout << "【代理】记录战果:已击败" << monster << ",更新任务日志。" << endl;}public:UltramanProxy(Fighter* real) : realUltraman(real) {}void fight(string monster) override {checkMonsterLevel(monster);    // 步骤1:检查(代理做)realUltraman->fight(monster);  // 步骤2:调用真实对象战斗recordResult(monster);         // 步骤3:记录(代理做)}
};int main() {Fighter* tiga = new Ultraman("迪迦");Fighter* proxy = new UltramanProxy(tiga);cout << "基地:发现怪兽哥尔赞,请求出击!" << endl;proxy->fight("哥尔赞");return 0;
}

代理可以分为多个种类

  • 远程代理:替你调用远程服务器的对象(比如调用云接口,代理帮你处理网络细节);
  • 虚拟代理:替你先占个坑,等真正需要时再创建对象(比如网页加载时先用占位图,图片加载完再替换);
  • 安全代理:替你检查权限(比如某些操作只有管理员能做,代理先拦下来验身份);
  • 智能引用:替你做些额外操作(比如对象被调用时自动计数,方便回收)。

3.3 装饰模式

装饰模式,也叫做包装模式,能动态的给一个对象增加额外的功能,并且这种扩充功能的方式对客户是透明的。它的精髓是:不用继承,用套娃的方式组合功能。比如给迪迦加光剑、加护盾,想加就套一层,想换就拆一层。。

当你需要更改一个对象的行为时,第一个跳入脑海的想法就是扩展它所属的类。比如给奥特曼添加新能力时,很容易想到用继承:基础迪迦→带光剑的迪迦(子类)→带护盾的迪迦(另一个子类)。但是由于继承的局限性,继承可能引发几个严重问题:

  • 继承是静态的:你无法在运行时更改已有对象的行为。比如创建了一个基础迪迦对象后,想临时给他加上光剑,没戏,必须提前创建光剑迪迦对象。
  • 子类只能有一个父类:如果想让迪迦同时拥有光剑和护盾,用继承就需要创建SwordAndShieldTiga子类;如果再加一个强化装甲,又要创建…… 功能越多,子类数量会爆炸式增长(n种功能可能需要2ⁿ个子类)。

继承实现的问题示例(奥特曼能力扩展)

class BasicTiga {
public:void fight() {cout << "基础迪迦:基础格斗" << endl;}
};class SwordTiga : public BasicTiga {
public:void fight() {BasicTiga::fight();cout << "带光剑:光剑劈砍" << endl;}
};class ShieldTiga : public BasicTiga {
public:void fight() {BasicTiga::fight();cout << "带护盾:能量防御" << endl;}
};class SwordShieldTiga : public BasicTiga {
public:void fight() {BasicTiga::fight();cout << "带光剑:光剑劈砍" << endl;cout << "带护盾:能量防御" << endl;}
};// 问题:每加一个组合,就要新增一个子类,扩展性极差
int main() {BasicTiga basic;SwordTiga sword;ShieldTiga shield;SwordShieldTiga swordShield;cout << "=== 基础迪迦 ===" << endl;basic.fight();cout << "\n=== 光剑迪迦 ===" << endl;sword.fight();cout << "\n=== 护盾迪迦 ===" << endl;shield.fight();cout << "\n=== 光剑+护盾迪迦 ===" << endl;swordShield.fight();return 0;
}

解决继承问题的核心是:用聚合或组合替代继承。

两者的工作方式几乎一样:一个对象包含指向另一个对象的引用,并将部分工作委派给引用对象;而继承是让子类直接继承父类的行为。

这种方式的优势:

  • 可以在运行时动态替换 “被委派的对象”,从而改变当前对象的行为;
  • 一个对象可以包含多个引用,同时委派给多个对象,实现多行为组合(无需创建大量子类)。
// 抽象能力接口(可被组合的“小帮手”)
class Ability {
public:virtual void use() = 0;virtual ~Ability() = default;
};// 光剑能力
class Sword : public Ability {
public:void use() override {cout << "使用光剑劈砍" << endl;}
};// 护盾能力
class Shield : public Ability {
public:void use() override {cout << "展开能量护盾" << endl;}
};class Tiga{
private:Ability* ability1 = nullptr; // 聚合第一个能力Ability* ability2 = nullptr; // 聚合第二个能力public:void setAbility1(Ability* a) {ability1 = a;}void setAbility2(Ability* a) {ability2 = a;}void fight(){cout << "基础迪迦:基础格斗" << endl;if(ability1) ability1->use();if(ability2) ability2->use();}
};int main() {Tiga tiga;Sword sword;Shield shield;cout << "=== 基础迪迦 ===" << endl;tiga.fight();cout << "\n=== 光剑迪迦 ===" << endl;tiga.setAbility1(&sword);tiga.fight();cout << "\n=== 光剑+护盾迪迦 ===" << endl;tiga.setAbility2(&shield);tiga.fight();return 0;
}

聚合解决了继承的问题,但还可以更规范 —— 这就是装饰模式。装饰模式的核心是 “封装器”:

封装器是一个能与 “目标对象” 连接的对象,它包含与目标对象相同的接口,会将所有请求委派给目标对象;同时,封装器可以在委派前后添加额外处理,从而改变行为。

一个简单的封装器能被称为 “装饰”,需满足:

  • 与被装饰对象实现相同的接口(客户端看来无差别);
  • 内部持有被装饰对象的引用(可以是任意实现该接口的对象);
  • 支持嵌套组合(一个装饰可以被另一个装饰包装,叠加行为)。

以奥特曼能力扩展为例,装饰模式的结构如下:

  • 抽象组件(Component):定义核心接口(如奥特曼的战斗能力);
  • 具体组件(Concrete Component):基础实现(如基础迪迦);
  • 抽象装饰器(Decorator):实现组件接口,持有组件引用(统一装饰结构);
  • 具体装饰器(Concrete Decorator):添加具体额外功能(如光剑装饰、护盾装饰)。
// 抽象能力接口
class Ultraman {
public:virtual void fight() = 0; // 战斗方法(装饰器和被装饰对象都要实现)virtual ~Ultraman() = default;
};// 具体组件:基础迪迦(无装饰)
class BasicTiga : public Ultraman {
public:void fight() override {cout << "基础迪迦:基础格斗术" << endl;}
};// 抽象装饰器
class UltramanDecorator : public Ultraman {
protected:Ultraman* ultraman; // 被装饰的对象(可以是基础组件或其他装饰)public:UltramanDecorator(Ultraman* u) : ultraman(u) {}// 委派给被装饰对象(具体装饰器会扩展此方法)void fight() override {if(ultraman) ultraman->fight();}
};// 具体装饰器
class SwordDecorator : public UltramanDecorator {
public:SwordDecorator(Ultraman* u) : UltramanDecorator(u) {}void fight() override {UltramanDecorator::fight(); // 先执行被装饰对象的行为cout << "【光剑装饰】额外使用光剑劈砍" << endl; // 新增行为}
};class ShieldDecorator : public UltramanDecorator {
public:ShieldDecorator(Ultraman* u) : UltramanDecorator(u) {}void fight() override {UltramanDecorator::fight(); // 先执行被装饰对象的行为cout << "【护盾装饰】额外展开能量护盾" << endl; // 新增行为}
};// 客户端:自由组合装饰,形成行为栈
int main() {// 基础组件:基础迪迦Ultraman* basic = new BasicTiga();cout << "=== 基础迪迦 ===" << endl;basic->fight();cout << endl;// 装饰1:给基础迪迦套上光剑装饰Ultraman* withSword = new SwordDecorator(basic);cout << "=== 光剑迪迦 ===" << endl;withSword->fight();cout << endl;// 装饰2:给“光剑迪迦”再套上护盾装饰(嵌套组合)Ultraman* withSwordShield = new ShieldDecorator(withSword);cout << "=== 光剑+护盾迪迦 ===" << endl;withSwordShield->fight();// 释放资源(从外层装饰开始)delete withSwordShield;delete withSword;delete basic;return 0;
}

装饰模式的核心价值

  • 动态扩展:客户端可以在运行时给对象套上任意装饰(如先加光剑,战斗中再加护盾);
  • 自由组合:通过嵌套装饰形成 “行为栈”(光剑→护盾→强化装甲),无需为每个组合创建子类;
  • 接口一致:装饰器与被装饰对象接口相同,客户端无需区分,直接调用即可。

3.4 外观模式

为一组复杂的子系统提供一个统一的高层接口,客户端通过这个接口与子系统交互,而无需关心子系统内部的细节。

简单说就是:当一个任务需要多个子系统协同完成,客户端不用逐个调用每个子系统的方法,只需找一个 “总管”,总管会自动协调所有子系统完成准备,这就是外观模式的作用:简化复杂系统的交互,降低客户端与子系统的耦合。再简单来说就是:你不用知道后厨有多少人,跟服务员说来份套餐就行。

假设光之国的奥特曼出击前,需要三个子系统协同工作:

  • 能量系统(EnergySystem):给奥特曼充能(charge());
  • 武器系统(WeaponSystem):加载武器(loadWeapon());
  • 通讯系统(CommSystem):向总部报告出击计划(report())。

如果没有外观模式,客户端(比如奥特曼本人)需要手动调用三个系统的方法,且必须知道每个系统的细节(比如先充能、再装武器、最后报告,顺序不能错):

// 子系统1:能量系统
class EnergySystem {
public:void charge() {cout << "能量系统:给奥特曼充能至100%" << endl;}
};// 子系统2:武器系统
class WeaponSystem {
public:void loadWeapon() {cout << "武器系统:加载哉佩利敖光线枪" << endl;}
};// 子系统3:通讯系统
class CommSystem {
public:void report() {cout << "通讯系统:向总部报告出击计划" << endl;}
};// 客户端:奥特曼需要手动协调所有子系统
int main() {// 客户端必须知道所有子系统,并手动管理调用顺序EnergySystem energy;WeaponSystem weapon;CommSystem comm;cout << "奥特曼准备出击:" << endl;energy.charge();    // 步骤1:充能weapon.loadWeapon();// 步骤2:装武器comm.report();      // 步骤3:报告cout << "准备完成,出击!" << endl;return 0;
}

程序中类的业务逻辑将与第三方类的实现细节紧密耦合, 使得理解和维护代码的工作很难进行。因此引入一个外观类,它封装所有子系统的交互逻辑,外观类内部持有子系统的引用,并且提供一个高层接口,在接口内部按正确顺序调用子系统的方法,客户端只需调用外观类的接口,无需关心子系统细节。

// 子系统1:能量系统
class EnergySystem {
public:void charge() {cout << "能量系统:充能至100%" << endl;}
};// 子系统2:武器系统
class WeaponSystem {
public:void loadWeapon() {cout << "武器系统:加载光线枪" << endl;}
};// 子系统3:通讯系统
class CommSystem {
public:void report() {cout << "通讯系统:报告总部" << endl;}
};// 外观类(Facade):统一接口,封装子系统交互
class AttackFacade {
private:// 外观类持有所有子系统的引用EnergySystem* energy;WeaponSystem* weapon;CommSystem* comm;public:// 初始化所有子系统AttackFacade() {energy = new EnergySystem();weapon = new WeaponSystem();comm = new CommSystem();}// 高层接口:封装出击准备的所有步骤(客户端只需调用这一个方法)void prepareAttack() {cout << "=== 开始出击准备 ===" << endl;energy->charge();       // 步骤1:充能weapon->loadWeapon();   // 步骤2:装武器comm->report();         // 步骤3:报告cout << "=== 准备完成,出击! ===" << endl;}~AttackFacade() {delete energy;delete weapon;delete comm;}
};// 客户端:只需与外观类交互,无需关心子系统
int main() {// 客户端只需要创建外观对象AttackFacade* attackFacade = new AttackFacade();// 调用一个接口,完成所有准备工作(无需知道子系统细节)attackFacade->prepareAttack();// 释放外观对象(子系统资源由外观类内部管理)delete attackFacade;return 0;
}

与适配器模式的区别

  • 外观模式:简化接口(为复杂子系统提供统一入口,不改变子系统接口);
  • 适配器模式:转换接口(让接口不兼容的对象能协同工作,改变接口形式)。

3.5 组合模式

组合模式核心思想是:将对象组合成树形结构,使客户端对单个对象和组合对象(由多个对象组成的整体)的使用具有一致性。

简单说就是:无论是单个元素还是元素的组合,客户端都可以用同样的方式操作,无需区分是单个还是整体。

用 “奥特曼团队” 场景理解

光之国的战斗单位有两种形式:单个奥特曼,独立战斗,有自己的战斗力;奥特曼小队,由多个奥特曼或更小的小队组成,整体战斗力是所有成员的总和。

如果客户端需要计算某个战斗单位的总战斗力,传统方式需要区分这是单个奥特曼还是一个小队,分别处理,而组合模式可以让客户端用统一的方法,无论传入的是单个奥特曼还是一个小队,都能得到正确结果。

组合模式的核心角色

  • 抽象组件(Component):定义单个对象和组合对象的共同接口(如 “获取战斗力”“添加成员” 等);
  • 叶子节点(Leaf):不可再分的单个对象(如单个奥特曼),实现抽象组件的接口,但不包含子组件;
  • 组合节点(Composite):由多个组件(叶子或其他组合)组成的整体(如奥特曼小队),实现抽象组件的接口,同时包含子组件的管理方法(添加、删除子组件)。
class UltramanComponent {   // 抽象组件:定义单个和组合对象的共同接口
public:virtual ~UltramanComponent() = default;virtual int getPower() = 0; // 核心接口:获取战斗力(单个和组合都需要)virtual void add(UltramanComponent* component) {} // 组合节点需要,叶子节点默认空实现virtual void remove(UltramanComponent* component) {} // 同理
};class SingleUltraman : public UltramanComponent {   //叶子节点:单个奥特曼(不可再分,无子类组件)
private:string name;int power;public:SingleUltraman(string n, int p) : name(n), power(p) {}int getPower() override{cout << name << "的战斗力:" << power << endl;return power;}
};class UltramanTeam : public UltramanComponent{      //组合节点:奥特曼小队(包含多个组件,可嵌套)
private:    string teamName;vector<UltramanComponent*> members;public:UltramanTeam(string n) : teamName(n) {}int getPower() override{int total = 0;cout << "=== 计算" << teamName << "的总战斗力 ===" << endl;for(auto member : members){total += member->getPower();}cout << teamName << "的战斗力:" << total << endl;return total;}void add(UltramanComponent* component) override{members.push_back(component);cout << teamName << "添加了新成员!" << endl;}void remove(UltramanComponent* component) override {for (auto it = members.begin(); it != members.end(); ++it) {if (*it == component) {members.erase(it);cout << teamName << "移除了一个成员!" << endl;return;}}}~UltramanTeam() {for (auto member : members) {delete member;}}
};int main() {UltramanComponent* tiga = new SingleUltraman("迪迦", 5000);     // 创建叶子节点(单个奥特曼)UltramanComponent* zero = new SingleUltraman("赛罗", 6000);UltramanComponent* zett = new SingleUltraman("泽塔", 4500);UltramanComponent* rookieTeam = new UltramanTeam("新手小队");   // 创建组合节点(小队):新手小队(包含泽塔)rookieTeam->add(zett); // 新手小队添加泽塔UltramanComponent* eliteTeam = new UltramanTeam("精英小队");    // 创建更高层组合节点(精英小队):包含迪迦、赛罗、新手小队eliteTeam->add(tiga);    // 添加单个奥特曼eliteTeam->add(zero);    // 添加单个奥特曼eliteTeam->add(rookieTeam); // 添加子小队(嵌套组合)cout << "最终总战斗力:" << eliteTeam->getPower() << endl;  // 客户端统一调用getPower(),无需区分是单个还是组合delete eliteTeam;return 0;
}

组合模式的核心价值在于客户端用相同的代码处理单个对象和组合对象,无需判断这是单个还是整体,简化了客户端逻辑。支持任意层次的嵌套组合,像搭积木一样构建复杂结构,新增叶子节点(如 “盖亚奥特曼”)或组合节点(如 “特殊任务小队”)时,只需实现UltramanComponent接口,客户端代码无需修改,符合开放封闭原则。

3.6 桥接模式

核心思想是:将抽象部分与它的实现部分分离,使它们可以独立地变化。

简单说就是:当一个对象存在多个独立变化的维度(比如奥特曼的形态和技能系统是两个独立维度,形态可分为基础/闪耀,技能可分为光线/格斗),通过桥接(用组合替代继承)将两个维度解耦,让每个维度可以单独扩展,避免因多维度组合导致的类爆炸。

假设光之国的奥特曼有两个变化维度:

  • 形态维度:基础形态、闪耀形态(形态不同,外观和基础属性不同);
  • 技能系统维度:光线技能(远程)、格斗技能(近战)(技能系统不同,攻击方式不同)。

如果用继承实现这两个维度的组合,会出现以下问题:

  • 基础形态 + 光线技能 → BasicLightUltraman
  • 基础形态 + 格斗技能 → BasicFightUltraman
  • 闪耀形态 + 光线技能 → ShinyLightUltraman
  • 闪耀形态 + 格斗技能 → ShinyFightUltraman

每新增一个形态或技能,都要新增多个子类(n 个形态 × m 个技能 = n×m 个子类),这就是 “类爆炸”。

class BasicLightUltraman {  // 基础形态+光线技能
public:void attack() { cout << "基础形态使用光线技能:哉佩利敖光线" << endl; }
};class BasicFightUltraman {  // 基础形态+格斗技能
public:void attack() { cout << "基础形态使用格斗技能:奥特拳" << endl; }
};class ShinyLightUltraman {  // 闪耀形态+光线技能
public:void attack() { cout << "闪耀形态使用光线技能:闪耀哉佩利敖光线" << endl; }
};class ShinyFightUltraman {  // 闪耀形态+格斗技能
public:void attack() { cout << "闪耀形态使用格斗技能:闪耀奥特拳" << endl; }
};int main() {BasicLightUltraman b1;ShinyFightUltraman s2;b1.attack();s2.attack();return 0;
}

桥接模式通过分离抽象与实现解决类爆炸的问题,这一点其实和建造者模式有些相似,但实现方式略有不同。具体来说:抽离 “技能系统” 作为独立的类层次(实现维度),定义技能的统一接口;保留形态作为主类层次(抽象维度),但在形态类中添加一个指向 “技能对象” 的引用(组合关系);形态类的行为(如攻击)不再通过继承绑定技能,而是委托给持有的技能对象,这个引用就是形态和技能之间的桥。

// 实现维度:技能系统的抽象接口(独立于形态)
class SkillSystem {
public:virtual void useSkill() = 0; // 所有技能必须实现“使用技能”接口virtual ~SkillSystem() = default;
};// 具体技能1:光线技能(实现技能接口)
class LightSkill : public SkillSystem {
public:void useSkill() override { cout << "使用哉佩利敖光线" << endl; }
};// 具体技能2:格斗技能(实现技能接口)
class FightSkill : public SkillSystem {
public:void useSkill() override { cout << "使用奥特拳" << endl; }
};// 抽象维度:形态的抽象接口(持有技能引用——桥接点)
class TigaForm {
protected:SkillSystem* skill; // 组合技能对象(桥接的核心)public:// 构造时传入技能系统(绑定一个技能)TigaForm(SkillSystem* s) : skill(s) {}virtual void attack() = 0; // 攻击行为(形态+技能的组合)virtual ~TigaForm() = default;
};// 具体形态1:基础形态(继承抽象形态,扩展形态特性)
class BasicForm : public TigaForm {
public:BasicForm(SkillSystem* s) : TigaForm(s) {}void attack() override {cout << "基础形态:";skill->useSkill(); // 委托给技能对象(通过桥接复用技能)}
};// 具体形态2:闪耀形态(继承抽象形态,扩展形态特性)
class ShinyForm : public TigaForm {
public:ShinyForm(SkillSystem* s) : TigaForm(s) {}void attack() override {cout << "闪耀形态:";skill->useSkill(); // 委托给技能对象(通过桥接复用技能)}
};// 客户端:自由组合形态和技能(维度独立扩展)
int main() {// 创建技能对象(实现维度)SkillSystem* light = new LightSkill();SkillSystem* fight = new FightSkill();// 组合1:基础形态 + 光线技能TigaForm* basicLight = new BasicForm(light);// 组合2:闪耀形态 + 格斗技能TigaForm* shinyFight = new ShinyForm(fight);// 调用攻击(形态和技能通过桥接协同工作)basicLight->attack();  // 基础形态:使用哉佩利敖光线shinyFight->attack();  // 闪耀形态:使用奥特拳// 扩展:新增“暗黑形态”(只需扩展抽象维度,不影响技能)class DarkForm : public TigaForm {public:DarkForm(SkillSystem* s) : TigaForm(s) {}void attack() override {cout << "暗黑形态:";skill->useSkill();}};TigaForm* darkLight = new DarkForm(light);darkLight->attack();  // 暗黑形态:使用哉佩利敖光线// 扩展:新增“防御技能”(只需扩展实现维度,不影响形态)class DefenseSkill : public SkillSystem {public:void useSkill() override {cout << "使用能量护盾" << endl;}};SkillSystem* defense = new DefenseSkill();TigaForm* basicDefense = new BasicForm(defense);basicDefense->attack();  // 基础形态:使用能量护盾delete basicLight;delete shinyFight;delete darkLight;delete basicDefense;delete light;delete fight;delete defense;return 0;
}

桥接模式就像奥特曼的形态-技能连接器:形态和技能独立变化,桥接通过组合让它们灵活搭配,既避免了类爆炸,又让两个维度可以自由扩展。

3.7 享元模式

享元模式的核心思想是通过共享技术复用系统中大量相似的对象,减少内存占用和对象创建开销。

简单说就是:当系统中存在大量基础属性相同、仅少数细节不同的对象时(比如光之国的基础奥特曼士兵,他们的颜色、基础技能完全一样,只有编号和当前位置不同),不需要为每个对象创建独立实例,而是共享一个 基础模板(即享元对象),只存储每个对象的独特细节,就像打印1000张相同格式的表格,只需一个表格模板,每张表格只填不同的数据。

假设光之国需要部署 1000 名基础奥特曼士兵守卫不同区域,这些士兵的核心属性高度一致:

  • 内部状态(共享属性):颜色(红银配色)、基础技能(奥特光束)、所属部队(光之国卫队);
  • 外部状态(独特属性):士兵编号(001~1000)、当前守卫区域(A 区~J 区)。

如果为每个士兵创建独立对象,会重复存储 1000 份相同的 “颜色、技能、部队” 信息,浪费大量内存。享元模式的解决方案是:

  • 创建一个基础士兵模板(享元对象),存储共享的内部状态;
  • 每个士兵的独特信息(编号、区域)作为外部状态,在使用时动态传入;
  • 所有士兵共享同一个模板,仅外部状态不同。
// 抽象享元:奥特曼士兵接口
class UltramanSoldier {
public:// 声明外部状态的操作:执行任务(需要传入编号和区域)virtual void mission(int id, string area) = 0;virtual ~UltramanSoldier() = default;
};// 具体享元:基础士兵模板(存储内部状态)
class BasicSoldier : public UltramanSoldier {
private:// 内部状态(共享属性:所有基础士兵都相同)string color;    // 颜色string skill;    // 基础技能string army;     // 所属部队public:// 构造时初始化内部状态(共享属性)BasicSoldier(string c, string s, string a) : color(c), skill(s), army(a) {}// 执行任务:使用内部状态 + 传入的外部状态(编号、区域)void mission(int id, string area) override {cout << "士兵" << id << "(" << color << ")在" << area << "执行任务:" << endl;cout << " - 所属部队:" << army << endl;cout << " - 使用技能:" << skill << "\n" << endl;}
};// 享元工厂:管理和缓存享元对象
class SoldierFactory {
private:// 缓存享元对象(key:内部状态标识,value:享元对象)unordered_map<string, UltramanSoldier*> soldiers;public:// 获取享元对象:如果存在则直接返回,否则创建并缓存UltramanSoldier* getSoldier(string color, string skill, string army) {// 用内部状态组合成key(标识唯一的享元)string key = color + "_" + skill + "_" + army;// 如果缓存中没有,创建新享元并加入缓存if (soldiers.find(key) == soldiers.end()) {soldiers[key] = new BasicSoldier(color, skill, army);cout << "【工厂】创建新的基础士兵模板:" << key << endl;} else {cout << "【工厂】复用已有的基础士兵模板:" << key << endl;}return soldiers[key];}// 析构:释放所有缓存的享元对象~SoldierFactory() {for (auto& pair : soldiers) {delete pair.second;}}
};// 客户端:获取享元对象,传入外部状态执行任务
int main() {// 创建享元工厂SoldierFactory* factory = new SoldierFactory();// 需要部署1000名士兵,但基础属性相同,只需共享一个模板// 第1个士兵:外部状态(001,A区)UltramanSoldier* soldier1 = factory->getSoldier("红银色", "奥特光束", "光之国卫队");soldier1->mission(001, "A区");// 第2个士兵:外部状态(002,B区),复用同一模板UltramanSoldier* soldier2 = factory->getSoldier("红银色", "奥特光束", "光之国卫队");soldier2->mission(002, "B区");// 第3个士兵:外部状态(003,C区),继续复用UltramanSoldier* soldier3 = factory->getSoldier("红银色", "奥特光束", "光之国卫队");soldier3->mission(003, "C区");// 释放资源delete factory;return 0;
}

可以看到,享元模式的核心角色有四个:

  • 抽象享元:定义享元对象的接口,声明外部状态的操作方法;
  • 具体享元:实现抽象享元,存储内部状态(共享属性),并通过方法接收外部状态(独特属性);
  • 享元工厂:管理享元对象,负责创建和缓存享元,确保相同内部状态的对象只被创建一次;
  • 客户端:使用享元工厂获取享元对象,并传入外部状态使用。

值得一提的是,享元模式与原型模式的区别在于享元模式是通过工厂缓存共享已有对象,适合大量相似对象的场景,而原型模式则是复制已有对象,适合对象创建复杂但数量不多的场景。

3.8 结构型设计模式总结

结构型模式就像对象世界的 “建筑法则”:

模式 功能
适配器 让不兼容的接口能合作
代理 给对象找个助手干杂活
装饰 动态给对象叠 buff
外观 给复杂系统找个总管
组合 让单个和整体能用同一套操作
桥接 解耦多维度,避免类爆炸
享元 共享相似对象,省内存

四、行为型模式

行为模式专注于描述对象之间的交互方式以及职责的分配方式。与结构模式不同,行为模式更关注对象之间的动态关系,而不是静态的结构。行为模式的核心目标是提高系统的灵活性和可维护性。

如果说结构型模式是给光之国搭积木(拼对象结构),那行为型模式就是给积木定规矩,明确奥特曼、补给站、战斗系统这些对象之间该怎么互动、责任该怎么分。它不关心对象长啥样,只关心对象之间的动态协作,比如迪迦怎么切换技能、补给站怎么通知小队、战斗流程怎么标准化。

4.1 策略模式

策略模式的核心思想是:将对象的不同行为封装成独立的策略类,让这些策略可以互相替换,从而使对象在运行时能灵活切换行为,而无需修改自身代码。

举个例子:迪迦有三招:光线(远程)、格斗(近战)、防御(自保)。要是用传统写法,得给迪迦塞一堆if-else,像给他焊死了三个按钮,想加个空中飞踢?得拆了原来的按钮重焊,代码直接僵住:

class Tiga {
public:enum AttackType { LIGHT, FIGHT, DEFENSE };void attack(AttackType type) {if (type == LIGHT) {cout << "使用哉佩利敖光线" << endl;} else if (type == FIGHT) {cout << "使用奥特拳" << endl;} else if (type == DEFENSE) {cout << "展开能量护盾" << endl;}// 新增攻击方式需加新的else if,违反开放封闭原则}
};

问题很明显,新增攻击方式必须修改Tiga类的attack方法,这违反了开放封闭原则,且攻击方式与奥特曼类紧耦合,无法复用,比如更坑的是如果赛罗也想用光线,还得把这段代码复制一遍。

策略模式的解法是“拆包”:把攻击方式从迪迦身上拆出来,做成独立的技能包,迪迦要攻击时,拿个技能包就能用,想换就换,自己完全不用改:

//抽象策略:定义攻击行为接口
class AttackStrategy {
public:virtual void attack() = 0; // 所有攻击策略必须实现的接口virtual ~AttackStrategy() = default;
};//具体策略:实现具体攻击方式
class LightAttack : public AttackStrategy{
public:void attack() override{cout << "使用哉佩利敖光线(远程攻击)" << endl;}};class FightAttack : public AttackStrategy {
public:void attack() override { cout << "使用奥特拳(近战攻击)" << endl;}
};class DefenseStrategy : public AttackStrategy {
public:void attack() override {cout << "展开能量护盾(防御)" << endl;}
};// 奥特曼类,使用策略执行行为
class Tiga {
private:AttackStrategy* strategy;
public:Tiga(AttackStrategy* s) : strategy(s) {}  //构造时默认策略void setStrategy(AttackStrategy* s) {strategy = s;}  //切换策略接口void fight() {cout << "迪迦准备攻击:";strategy->attack(); // 不关心具体是哪种攻击,只调用策略接口}
};int main() {AttackStrategy* Light = new LightAttack();AttackStrategy* Fight = new FightAttack();AttackStrategy* Defense = new DefenseStrategy();// 创建奥特曼,初始用光线策略Tiga tiga(Light);tiga.fight();// 战斗中切换策略tiga.setStrategy(Fight);tiga.fight();tiga.setStrategy(Defense);tiga.fight();return 0;
}

策略模式的核心价值在于行为与上下文解耦,且可动态切换行为,比继承的静态绑定灵活;易于扩展,消除条件判断,代码更清晰。想加空中飞踢?新增个FlyAttack技能包就行,迪迦一行代码不用改;赛罗想用光线?直接拿LightAttack包,不用复制代码。从此告别if-else堆成的垃圾山。

4.2 模板方法模式

假设你正在开发一款奥特曼战斗指挥系统,用于协助奥特曼处理敌人,系统需要接收敌人类型,指挥奥特曼完成发现敌人、分析弱点、攻击、生成战斗报告的完整流程,并返回统一的战斗结果。

该系统的首个版本仅支持处理怪兽类敌人(MonsterHandler)。后来出现了宇宙人,所以你又新增了AlienHandler类(处理宇宙人)。在后来,你又为机械体敌人新增了MachineHandler类。

久而久之,你发现三个类的核心流程都是发现→分析→攻击→报告,其中分析弱点和生成报告的逻辑几乎完全相同,但这些代码在三个类中重复编写。并且客户端需要根据敌人类型判断用哪个处理器,代码充斥着if-else:

// 客户端问题代码
void commandAttack(string enemyType) {if (enemyType == "怪兽") {MonsterHandler handler;handler.handle();} else if (enemyType == "宇宙人") {AlienHandler handler;handler.handle();} else if (enemyType == "机械体") {MachineHandler handler;handler.handle();}
}

模板方法模式的解法是:把固定流程写成“光之国战斗说明书”,通用步骤(分析弱点、写战报)印在说明书里,特殊步骤(怎么发现怪兽、怎么打宇宙人)让不同敌人的处理员自己填。这样既少写重复代码,指挥中心调用也不用再判断敌人类型了。

// 作战模板基类:定死流程,共享重复步骤
class Fighter {
protected:string enemy; // 敌人类型virtual void lockTarget() = 0;    // 步骤1:锁定目标virtual void analyzeWeakness() {  // 步骤2:分析弱点cout << "分析" << enemy << "弱点:连接光之国数据库→找到能量核心!" << endl;}virtual void attack() = 0;        // 步骤3:发动攻击virtual void report() {          // 步骤4:提交战报cout << "战报:成功击退" << enemy << ",over!\n" << endl;}public:Fighter(string e) : enemy(e) {}// 模板方法:固定作战流程(谁也改不了顺序!)void fight() {cout << "==== 开始对付" << enemy << " ====" << endl;lockTarget();    // 1. 锁定analyzeWeakness();// 2. 分析(共享)attack();        // 3. 攻击report();        // 4. 战报(共享)}
};// 对付怪兽
class MonsterFighter : public Fighter {
public:MonsterFighter() : Fighter("怪兽") {}
protected:void lockTarget() override {cout << "锁定:用地面雷达定位怪兽位置" << endl;}void attack() override {cout << "攻击:发射哉佩利敖光线轰能量核心" << endl;}
};// 对付宇宙人
class AlienFighter : public Fighter {
public:AlienFighter() : Fighter("宇宙人") {}
protected:void lockTarget() override {cout << "锁定:用太空站监测宇宙人飞船" << endl;}void attack() override {cout << "攻击:甩头镖切断飞船动力" << endl;}
};// 指挥中心:用多态调用,再也不用if-else了
int main() {Fighter* fighter1 = new MonsterFighter();fighter1->fight(); // 自动按怪兽流程打Fighter* fighter2 = new AlienFighter();fighter2->fight(); // 自动按宇宙人流程打delete fighter1;delete fighter2;return 0;
}

核心价值:标准化流程 + 个性化实现,就像做蛋糕,手册定死打蛋→和面→烘焙,你只需选巧克力味还是草莓味。新增敌人(比如 “亡灵怪兽”)?只需填个新的UndeadFighter,老代码不用动。

4.3 观察者模式

在光之国,有两种关键角色:装备补给站和奥特曼小队,装备补给站负责采购新装备,奥特曼小队则需要新装备提升战力。比如精英队急需新光剑,新手队等着新护盾,但现在的流程很麻烦:

要么小队天天跑补给站问“装备到了吗”,多数时候都是白跑;要么补给站不管谁需要,一有新装备就给所有小队发通知,新手队收到光剑到货的消息,但是根本用不上,纯属干扰。

这就是典型的要么浪费小队时间,要么浪费补给站资源的矛盾,而观察者模式能完美解决。

观察者模式的解法是:给补给站加订阅功能,小队只订阅自己关心的装备,补给站到货后,只通知订阅的小队,不打扰其他人。就像你订阅了光剑到货提醒,护盾到货就不会给你发消息。

// 订阅者(观察者)接口:所有小队都要遵守的规矩
class Observer {
public:// 接收装备通知:参数是新到的装备名virtual void onEquipArrive(string equipName) = 0;virtual ~Observer() = default;
};// 发布者(被观察者)接口:补给站要遵守的规矩
class Publisher {
public:virtual void addObserver(Observer* obs, string targetEquip) = 0; // 订阅(指定关心的装备)virtual void removeObserver(Observer* obs) = 0; // 取消订阅virtual void notify(string newEquip) = 0; // 通知订阅者(新装备到货)virtual ~Publisher() = default;
};// 具体发布者:光之国装备补给站
class SupplyStation : public Publisher {
private:vector<pair<Observer*, string>> observers;  // 存“订阅者-关心的装备”对应关系public:// 小队订阅:传自己和关心的装备void addObserver(Observer* obs, string targetEquip) override {observers.push_back({obs, targetEquip});cout << "补给站:某小队订阅了【" << targetEquip << "】通知\n" << endl;}// 小队取消订阅void removeObserver(Observer* obs) override {for (auto it = observers.begin(); it != observers.end(); ++it) {if (it->first == obs) {cout << "补给站:某小队取消了订阅\n" << endl;observers.erase(it);return;}}}// 新装备到货:通知所有订阅了该装备的小队void notify(string newEquip) override {cout << "补给站:新装备【" << newEquip << "】到货!" << endl;for (auto& pair : observers) {// 只通知关心这个装备的小队if (pair.second == newEquip) {pair.first->onEquipArrive(newEquip);}}cout << endl;}// 模拟补给站采购到新装备void getNewEquip(string equip) {notify(equip); // 到货后自动触发通知}
};// 4. 具体订阅者1:精英小队(只关心光剑)
class EliteTeam : public Observer {
public:void onEquipArrive(string equipName) override {cout << "精英小队:收到!【" << equipName << "】终于到了,马上来取!" << endl;}
};// 5. 具体订阅者2:新手小队(只关心护盾)
class RookieTeam : public Observer {
public:void onEquipArrive(string equipName) override {cout << "新手小队:太好了!【" << equipName << "】到货,这下安全了!" << endl;}
};// 客户端:模拟整个流程
int main() {// 创建补给站(发布者)SupplyStation* station = new SupplyStation();// 创建两个小队(订阅者)Observer* elite = new EliteTeam();Observer* rookie = new RookieTeam();// 1. 小队订阅自己关心的装备station->addObserver(elite, "光剑");    // 精英队订光剑station->addObserver(rookie, "护盾");   // 新手队订护盾// 2. 补给站到了光剑:只通知精英队station->getNewEquip("光剑");// 3. 新手队临时取消订阅(不需要了)station->removeObserver(rookie);// 4. 补给站到了护盾:只通知还订阅的精英队(但精英队不关心,所以没响应)station->getNewEquip("护盾");// 释放资源delete station;delete elite;delete rookie;return 0;
}

核心价值:按需沟通,解决一方瞎等、一方瞎喊的问题。比如后续加“赛罗队订阅头镖”,只需加个ZeroTeam类,补给站代码不用改。

4.4 迭代器模式

在光之国,有两种常用的战力储备集合:一种是奥特曼小队(用列表存队员,要正序点名),另一种是装备储备栈(用栈存装备,要倒序查看 —— 最后放的装备先拿)。

但指挥中心遍历这些集合时遇到了麻烦:遍历小队得用for循环逐个取列表元素,遍历装备栈又得用pop()逐个弹元素,不仅要记住每种集合的内部结构,新增集合(比如用树存的 “技能库”)还得重写遍历逻辑。

// 小队(用vector存)
class UltramanTeam {
public:vector<string> members; // 内部结构暴露
};// 装备栈(用stack存)
class EquipmentStack {
public:stack<string> equipments; // 内部结构暴露
};// 客户端遍历:得记两种方式,麻烦!
void checkReserve(UltramanTeam& team, EquipmentStack& eqStack) {// 遍历小队:记vector的遍历方式cout << "小队点名:";for (int i=0; i<team.members.size(); i++) {cout << team.members[i] << " ";}// 遍历装备栈:记stack的遍历方式cout << "\n装备查看:";while (!eqStack.equipments.empty()) {cout << eqStack.equipments.top() << " ";eqStack.equipments.pop(); // 还会改变栈的原始数据!}
}

问题很明显,首先客户端必须知道集合的内部结构(小队用 vector、装备用 stack),其次遍历逻辑和集合高度绑定,新增集合(如技能树)就得加新遍历代码。

迭代器模式的解法是:给每个集合配个向导(迭代器),向导知道集合的内部结构(vector还是stack),指挥中心只需问向导还有下一个吗?下一个是谁?,不用管集合咋存的。

就像你去光之国参观,向导带你走(迭代器遍历),你不用管路是直的还是弯的,跟着走就行:

// 1. 基础数据结构(简单存储信息)
struct Ultraman { string name; };  // 奥特曼
struct Equipment { string name; }; // 装备// 2. 迭代器接口(所有迭代器都要遵守的规矩)
template <typename T>
class Iterator {
public:virtual bool hasNext() = 0; // 还有下一个元素吗?virtual T next() = 0;       // 取到下一个元素virtual ~Iterator() = default;
};// 3. 聚合接口(所有集合都要能提供迭代器)
template <typename T>
class Aggregate {
public:virtual Iterator<T>* getIterator() = 0; // 给一个迭代器virtual ~Aggregate() = default;
};// 4. 独立的小队迭代器
class TeamIterator : public Iterator<Ultraman> {
private:vector<Ultraman>& members; // 接收小队的成员集合(引用,不拷贝)int index = 0;             // 当前遍历位置
public:// 构造时直接传入要遍历的成员集合TeamIterator(vector<Ultraman>& m) : members(m) {}bool hasNext() override {return index < members.size(); // 没到末尾就有下一个}Ultraman next() override {return members[index++]; // 取当前元素,位置后移}
};// 5. 奥特曼小队集合
class UltramanTeam : public Aggregate<Ultraman> {
private:vector<Ultraman> members; // 内部存储(不暴露)
public:// 给小队加队员void addMember(Ultraman u) {members.push_back(u);}// 提供迭代器(直接new独立的TeamIterator)Iterator<Ultraman>* getIterator() override {return new TeamIterator(members); // 把内部成员传给迭代器}
};// 6. 独立的装备栈迭代器
class StackIterator : public Iterator<Equipment> {
private:vector<Equipment> temp; // 临时存栈元素(避免破坏原栈)int index = 0;          // 当前遍历位置
public:// 构造时传入要遍历的装备栈,先转存到vectorStackIterator(stack<Equipment>& s) {stack<Equipment> tempStack = s; // 拷贝原栈,不修改原数据while (!tempStack.empty()) {temp.push_back(tempStack.top());tempStack.pop();}}bool hasNext() override {return index < temp.size();}Equipment next() override {return temp[index++];}
};// 7. 装备栈集合
class EquipmentStack : public Aggregate<Equipment> {
private:stack<Equipment> equipments; // 内部存储(不暴露)
public:// 给栈加装备void pushEquipment(Equipment e) {equipments.push(e);}// 提供迭代器(直接new独立的StackIterator)Iterator<Equipment>* getIterator() override {return new StackIterator(equipments); // 把内部栈传给迭代器}
};// 客户端:简单遍历
int main() {// 准备小队数据UltramanTeam team;team.addMember({"迪迦"});team.addMember({"赛罗"});// 准备装备栈数据EquipmentStack eqStack;eqStack.pushEquipment({"光剑"});eqStack.pushEquipment({"护盾"});// 遍历小队(统一接口)Iterator<Ultraman>* teamIt = team.getIterator();cout << "小队点名:";while (teamIt->hasNext()) {cout << teamIt->next().name << " ";}// 遍历装备栈(统一接口)Iterator<Equipment>* eqIt = eqStack.getIterator();cout << "\n装备查看:";while (eqIt->hasNext()) {cout << eqIt->next().name << " ";}// 释放资源delete teamIt;delete eqIt;return 0;
}

核心价值:统一遍历接口,指挥中心不用记集合的内部结构,新增集合(比如用树存的技能库),只需加个技能库向导,遍历代码不用改。

4.5 责任链模式

奥特曼申请任务要过四关:IP 过滤→缓存检查→身份认证→权限匹配。原来的代码把四关堆在一个函数里,新增一关(比如队员数量检查)就得改整个函数:

// 问题代码:所有检查堆在一起
bool checkTaskRequest(TaskRequest req) {// 1. IP过滤检查if (isMaliciousIP(req.ip)) {cout << "IP被拦截:" << req.ip << endl;return false;}// 2. 缓存检查if (hasCache(req.ultraId, req.taskId)) {cout << "返回缓存:" << req.ultraId << "已申请过" << req.taskId << endl;return true; // 直接返回,不走后续}// 3. 身份认证if (!authenticate(req.ultraId)) {cout << "身份无效:" << req.ultraId << endl;return false;}// 4. 权限检查if (!checkPermission(req.ultraId, req.taskLevel)) {cout << "权限不足:" << req.ultraId << "无法申请" << req.taskLevel << "任务" << endl;return false;}// 所有检查通过,提交任务submitTask(req);return true;
}

很显然,代码臃肿,阅读和维护成本高。新增或修改检查步骤要改整个函数,违反开放封闭原则,且检查逻辑无法复用(比如其他系统需要 “IP 过滤 + 身份认证”,只能复制代码);

责任链模式的解法是:把每关拆成一个关卡员,连成一条链,申请请求从第一关开始传,通不过就拦截,通过了就交给下一关。新增关卡?只需加个新关卡员,插在链里就行。

就像光之国的任务申请窗口,第一关查IP,第二关查缓存,第三关查身份,第四关查权限,每个窗口只做自己的事:

// 1. 任务请求:包含申请的关键信息
struct TaskRequest {string ultraId;    // 奥特曼IDstring taskId;     // 任务IDstring ip;         // 申请IPstring taskLevel;  // 任务等级(普通/精英/首领)
};// 2. 处理者接口:所有“把关人”都要遵守的规矩
class Handler {
protected:Handler* nextHandler; // 下一个处理者(责任链的核心)
public:Handler() : nextHandler(nullptr) {}// 设置下一个处理者(链式连接)void setNext(Handler* next) {nextHandler = next;}// 处理请求(核心方法,子类实现具体检查)virtual bool handleRequest(TaskRequest req) = 0;virtual ~Handler() = default;
};//IP 过滤处理者:拦截恶意 IP
class IPFilterHandler : public Handler {
public:bool handleRequest(TaskRequest req) override {// 模拟:IP以"192.168.1."开头视为恶意if (req.ip.find("192.168.1.") == 0) {cout << "[IP过滤] 拦截恶意IP:" << req.ip << endl;return false; // 拦截请求,不传递}// 检查通过,交给下一个处理者(如果有的话)if (nextHandler != nullptr) {return nextHandler->handleRequest(req);}return true; // 没有下一个,默认通过}
};//缓存检查处理者:重复申请返回缓存
class CacheHandler : public Handler {
public:bool handleRequest(TaskRequest req) override {// 模拟:迪迦(ULTRA001)申请过任务TASK001,有缓存if (req.ultraId == "ULTRA001" && req.taskId == "TASK001") {cout << "[缓存检查] 重复申请:" << req.ultraId << "-" << req.taskId << ",返回缓存" << endl;return true; // 直接返回,不传递}// 检查通过,交给下一个if (nextHandler != nullptr) {return nextHandler->handleRequest(req);}return true;}
};//身份认证处理者:验证奥特曼是否注册
class AuthHandler : public Handler {
public:bool handleRequest(TaskRequest req) override {// 模拟:只有ID以"ULTRA"开头的是注册奥特曼if (req.ultraId.find("ULTRA") != 0) {cout << "[身份认证] 无效身份:" << req.ultraId << endl;return false; // 拦截}// 检查通过,交给下一个if (nextHandler != nullptr) {return nextHandler->handleRequest(req);}return true;}
};//权限检查处理者:验证是否有权限申请任务
class PermissionHandler : public Handler {
public:bool handleRequest(TaskRequest req) override {// 模拟:精英权限(ID含"ELITE")才能申请首领任务if (req.taskLevel == "首领" && req.ultraId.find("ELITE") == string::npos) {cout << "[权限检查] 权限不足:" << req.ultraId << "无法申请首领任务" << endl;return false; // 拦截}// 所有检查通过,提交任务cout << "[权限检查] 所有检查通过,提交任务:" << req.taskId << endl;return true;}
};int main() {// 1. 创建所有处理者Handler* ipFilter = new IPFilterHandler();Handler* cache = new CacheHandler();Handler* auth = new AuthHandler();Handler* permission = new PermissionHandler();// 2. 构建责任链:IP过滤 → 缓存检查 → 身份认证 → 权限检查ipFilter->setNext(cache);cache->setNext(auth);auth->setNext(permission);// 3. 测试3个不同的任务申请cout << "=== 测试1:恶意IP申请 ===" << endl;ipFilter->handleRequest({"ULTRA001", "TASK001", "192.168.1.100", "普通"});cout << "\n=== 测试2:重复申请(有缓存) ===" << endl;ipFilter->handleRequest({"ULTRA001", "TASK001", "192.168.2.100", "普通"});cout << "\n=== 测试3:合法申请(精英奥特曼申请首领任务) ===" << endl;ipFilter->handleRequest({"ELITE002", "TASK002", "192.168.2.200", "首领"});// 释放资源delete ipFilter;delete cache;delete auth;delete permission;return 0;
}

核心价值:分工明确,灵活调整,想加队员数量检查?新增个TeamSizeHandler,插在身份认证和权限检查之间就行;想换顺序?调整setNext的指向,不用改关卡员内部代码。

4.6 命令模式

在光之国,战士们通过作战控制台触发战斗指令,比如发射光线、召唤队友、开启护盾。最开始的控制台设计很简单:每个按钮对应一个指令,用子类实现不同按钮的点击逻辑。但很快问题就来了:

最开始,控制台只有 3 个按钮,我们为每个按钮写了子类:

  • LightButton:点击发射光线
  • SummonButton:点击召唤队友
  • ShieldButton:点击开启护盾

后续新增撤退、能量补给指令,就得新增RetreatButton、SupplyButton,控制台有10个指令就需要10个子类,子类爆炸。后续增加快捷面板、语音指令,同样需要使用控制台指令,如果直接复制代码,后续修改就得改3处。并且按钮子类直接包含奥特曼的战斗逻辑(比如LightButton里写ultra->fireLight()),控制台代码和奥特曼的业务逻辑绑死,改奥特曼的技能就得动按钮代码。

// 问题代码:每个按钮一个子类,耦合业务逻辑
class Button {
public:virtual void onClick() = 0;
};// 发射光线按钮子类
class LightButton : public Button {
private:Ultraman* ultra;
public:LightButton(Ultraman* u) : ultra(u) {}void onClick() override {cout << "光线按钮点击:";ultra->fireLight(); // 直接写业务逻辑,耦合!}
};// 召唤队友按钮子类
class SummonButton : public Button {
private:Ultraman* ultra;
public:SummonButton(Ultraman* u) : ultra(u) {}void onClick() override {cout << "召唤按钮点击:";ultra->summonTeammate(); // 重复的耦合逻辑}
};

命令模式的解法是:加个指令中间件,按钮不直接调用迪迦的技能,而是触发一个指令,指令再调用迪迦的技能。就像你按控制台按钮,按钮发发射光线指令给迪迦,迪迦再执行,按钮和迪迦彻底没关系,改技能只需改指令。

控制台按钮要做的只有一件事:点击时触发绑定的命令(调用命令的execute()方法)。剩下的所有工作,比如找到对应的奥特曼、调用他的技能全由命令对象自己处理,控制台完全不用管。

//先写奥特曼类,只关注能做什么,不关心谁触发:
class Ultraman {
private:string name;
public:Ultraman(string n) : name(n) {}// 业务方法1:发射光线void fireLight() { cout << name << "发射哉佩利敖光线!" << endl; }// 业务方法2:召唤队友void summonTeammate() { cout << name << "召唤赛罗支援!" << endl; }// 业务方法3:开启护盾void openShield() { cout << name << "开启能量护盾!" << endl; }
};//定义命令接口,保证发送者(按钮)能 “无差别” 调用
class Command {
public:virtual void execute() = 0; // 核心:执行指令virtual ~Command() = default;
};//实现具体命令,每个指令对应一个命令类
// 具体命令1:发射光线命令
class LightCommand : public Command {
private:Ultraman* ultra; // 指令执行者(奥特曼)
public:// 构造时绑定“执行者”LightCommand(Ultraman* u) : ultra(u) {}// 执行指令:调用奥特曼的发射光线方法void execute() override { ultra->fireLight(); }
};// 具体命令2:召唤队友命令
class SummonCommand : public Command {
private:Ultraman* ultra;
public:SummonCommand(Ultraman* u) : ultra(u) {}void execute() override { ultra->summonTeammate(); }
};// 具体命令3:开启护盾命令
class ShieldCommand : public Command {
private:Ultraman* ultra;
public:ShieldCommand(Ultraman* u) : ultra(u) {}void execute() override { ultra->openShield(); }
};//定义发送者,按钮不再用子类,只持有一个命令对象,点击时触发命令的execute()
class ConsoleButton {
private:string btnName;   // 按钮名称(比如“光线按钮”)Command* command; // 持有命令对象(核心:中间层)
public:ConsoleButton(string name) : btnName(name) {}// 绑定命令:给按钮设置要执行的指令void setCommand(Command* cmd) { command = cmd; }// 点击事件:触发命令执行(无业务逻辑!)void onClick() {cout << "点击【" << btnName << "】:";if (command != nullptr) {command->execute(); // 只调用命令,不碰奥特曼逻辑}}
};int main() {// 创建执行者:迪迦奥特曼Ultraman* tiga = new Ultraman("迪迦");// 创建命令:把迪迦和具体业务绑定Command* lightCmd = new LightCommand(tiga);    // 迪迦+发射光线Command* summonCmd = new SummonCommand(tiga);  // 迪迦+召唤队友Command* shieldCmd = new ShieldCommand(tiga);  // 迪迦+开启护盾// 创建发送者:控制台按钮(无业务逻辑)ConsoleButton* lightBtn = new ConsoleButton("光线按钮");ConsoleButton* summonBtn = new ConsoleButton("召唤按钮");ConsoleButton* shieldBtn = new ConsoleButton("护盾按钮");// 4. 绑定命令:给按钮分配要执行的指令lightBtn->setCommand(lightCmd);summonBtn->setCommand(summonCmd);shieldBtn->setCommand(shieldCmd);// 测试:点击按钮触发指令lightBtn->onClick();   // 点击【光线按钮】:迪迦发射哉佩利敖光线!summonBtn->onClick();  // 点击【召唤按钮】:迪迦召唤赛罗支援!shieldBtn->onClick();  // 点击【护盾按钮】:迪迦开启能量护盾!// 灵活扩展:同一命令绑定到不同按钮(比如快捷面板按钮)ConsoleButton* quickLightBtn = new ConsoleButton("快捷光线按钮");quickLightBtn->setCommand(lightCmd); // 复用“发射光线”命令cout << "\n点击【快捷光线按钮】:";quickLightBtn->onClick(); // 同样触发迪迦发射光线// 释放资源delete tiga;delete lightCmd;delete summonCmd;delete shieldCmd;delete lightBtn;delete summonBtn;delete shieldBtn;delete quickLightBtn;return 0;
}

核心价值:解耦发送者和执行者,按钮(发送者)不用知道迪迦(执行者)是谁,指令(中间件)帮他们牵线。新增撤退按钮?只需加个RetreatCommand,按钮和迪迦都不用改。

4.7 备忘录模式

光之国的战士们在战斗中常要切换形态,假设迪迦从基础形态切到闪耀形态,能量会骤降;切到暗黑形态,技能CD会重置。有次迪迦为了扛住怪兽攻击,紧急切到闪耀形态,却发现能量只剩30%,连必杀技都放不出来,想撤回却没办法,就像打游戏没存档,死了只能重来。。

光之国的技术官听到后,想给形态切换系统加撤销功能,每次切换前保存当前形态的状态(能量值、技能 CD、形态名称),需要时能恢复。但一开始就遇到了麻烦。

技术官最初的想法很直接,指挥中心直接读取迪迦的状态(能量、CD),保存下来。但迪迦的核心状态是私有变量,战士的能量值、技能CD不能暴露给外部系统,防止被敌人破解。

如果强行把这些变量改成公有,又会引发新问题:以后想给形态加新状态,所有读取状态的代码都要改;而且任何人都能修改迪迦的能量值,完全失控。

备忘录模式的破局思路:既然私有变量只有迪迦自己能访问,那不如切换形态前,让他自己把能量、CD打包成形态秘钥(备忘录)交给记录器;想撤销时,记录器把秘钥还给他,他用秘钥恢复状态,全程没人能偷看秘钥里的内容。

// 备忘录(Memento):迪迦的形态秘钥,只让Ultraman访问
class UltramanMemento {
private:// 私有状态:只有友元Ultraman能访问string formName;  // 形态名称(基础/闪耀/暗黑)int energy;       // 能量值int skillCD;      // 技能CD(秒)// 友元声明:允许Ultraman读写这些私有状态friend class Ultraman;// 构造函数私有:只能由Ultraman创建UltramanMemento(string name, int e, int cd) : formName(name), energy(e), skillCD(cd) {}
};// 原发器(Originator):迪迦,自己管理状态和备忘录
class Ultraman {
private:string currentForm;  // 当前形态(私有)int currentEnergy;   // 当前能量(私有)int currentSkillCD;  // 当前技能CD(私有)public:// 初始化迪迦状态(基础形态)Ultraman() : currentForm("基础形态"), currentEnergy(100), currentSkillCD(0) {cout << "迪迦初始化:" << currentForm << ",能量" << currentEnergy << ",CD" << currentSkillCD << "秒\n" << endl;}// 创建备忘录:保存当前状态(只有迪迦能调用)UltramanMemento* saveToMemento() {cout << "迪迦:保存当前状态到秘钥——" << currentForm << "(能量" << currentEnergy << ")" << endl;// 返回新的备忘录,用自己的私有状态初始化return new UltramanMemento(currentForm, currentEnergy, currentSkillCD);}// 从备忘录恢复状态:用秘钥回滚void restoreFromMemento(UltramanMemento* memento) {// 读取备忘录的私有状态(友元权限)currentForm = memento->formName;currentEnergy = memento->energy;currentSkillCD = memento->skillCD;cout << "迪迦:从秘钥恢复状态——" << currentForm << ",能量" << currentEnergy << ",CD" << currentSkillCD << "秒\n" << endl;}// 3. 切换形态(模拟业务操作,会改变状态)void switchForm(string newForm, int newEnergy, int newCD) {cout << "迪迦:切换到" << newForm << ",能量变为" << newEnergy << ",CD变为" << newCD << "秒" << endl;currentForm = newForm;currentEnergy = newEnergy;currentSkillCD = newCD;}// 查看当前状态(方便调试)void showCurrentState() {cout << "迪迦当前状态:" << currentForm << ",能量" << currentEnergy << ",CD" << currentSkillCD << "秒\n" << endl;}
};// 负责人:指挥中心的形态历史记录器,管理备忘录
class FormHistory {
private:stack<UltramanMemento*> mementoStack;  // 用栈存备忘录(最新的在栈顶)public:// 保存备忘录到栈void pushMemento(UltramanMemento* m) {mementoStack.push(m);cout << "指挥中心:已保存形态秘钥,当前历史数:" << mementoStack.size() << "\n" << endl;}// 取出最近的备忘录(撤销时用)UltramanMemento* popMemento() {if (mementoStack.empty()) return nullptr;UltramanMemento* top = mementoStack.top();mementoStack.pop();cout << "指挥中心:取出最近的形态秘钥,剩余历史数:" << mementoStack.size() << endl;return top;}// 析构:释放所有备忘录(避免内存泄漏)~FormHistory() {while (!mementoStack.empty()) {delete mementoStack.top();mementoStack.pop();}}
};int main() {// 初始化角色:迪迦 + 指挥中心记录器Ultraman tiga;FormHistory history;// 第一次切换:基础形态 → 闪耀形态(先保存再切换)UltramanMemento* m1 = tiga.saveToMemento();  // 迪迦保存基础形态history.pushMemento(m1);                     // 记录器存秘钥tiga.switchForm("闪耀形态", 30, 10);         // 切换到闪耀形态(能量骤降)tiga.showCurrentState();                     // 查看切换后的状态// 迪迦发现能量不够,请求撤销:恢复到基础形态cout << "迪迦:能量不够!请求撤销到上一个形态!" << endl;UltramanMemento* lastMemento = history.popMemento();  // 记录器取最近的秘钥tiga.restoreFromMemento(lastMemento);                 // 迪迦用秘钥恢复delete lastMemento;  // 恢复后释放备忘录(避免内存泄漏)// 第二次切换:基础形态 → 暗黑形态(再保存再切换)UltramanMemento* m2 = tiga.saveToMemento();  // 迪迦保存基础形态history.pushMemento(m2);                     // 记录器存秘钥tiga.switchForm("暗黑形态", 70, 5);          // 切换到暗黑形态tiga.showCurrentState();return 0;
}

核心价值:安全存档,灵活撤回,迪迦的私有状态不暴露,指挥中心只存秘钥不读内容;新增形态(比如空中形态),只需改迪迦和秘钥,指挥中心不用动。

4.8 状态模式

光之国的迪迦有三种核心形态:基础形态(平衡)、闪耀形态(高攻)、暗黑形态(高防)。每种形态下,他的攻击方式、防御能力、能量消耗完全不同,比如基础形态用哉佩利敖光线,闪耀形态用闪耀光线,暗黑形态则侧重暗黑护盾。

  • 基础形态:光线(耗 20 能量)、普通护盾(减 30% 伤);
  • 闪耀形态:闪耀光线(耗 50 能量)、闪耀护盾(减 80% 伤);
  • 暗黑形态:暗黑爪击(耗 15 能量)、暗黑屏障(耗 10 能量,挡一次伤);

没引入状态模式前,迪迦的战斗类里,每个技能方法(攻击、防御)都要先判断当前是什么形态,再执行对应逻辑。新增形态时,所有技能方法都要改,牵一发而动全身。

// 迪迦类:用if-else判断形态,代码臃肿
class UltramanTiga {
private:string currentForm;  // 当前形态:基础/闪耀/暗黑int energy;          // 能量值public:UltramanTiga() : currentForm("基础形态"), energy(100) {}// 切换形态(只是改字符串,没解决行为问题)void switchForm(string form) {currentForm = form;cout << "迪迦切换到" << currentForm << ",当前能量:" << energy << "\n" << endl;}// 攻击方法:堆满if-else判断形态void attack() {if (currentForm == "基础形态") {if (energy >= 20) {cout << "基础形态:发射哉佩利敖光线!消耗20能量" << endl;energy -= 20;} else {cout << "能量不足,无法使用基础攻击!" << endl;}} else if (currentForm == "闪耀形态") {if (energy >= 50) {cout << "闪耀形态:发射闪耀光线!消耗50能量" << endl;energy -= 50;} else {cout << "能量不足,无法使用闪耀攻击!" << endl;}} else if (currentForm == "暗黑形态") {if (energy >= 15) {cout << "暗黑形态:使用暗黑爪击!消耗15能量" << endl;energy -= 15;} else {cout << "能量不足,无法使用暗黑攻击!" << endl;}}cout << "当前剩余能量:" << energy << "\n" << endl;}// 防御方法:同样堆满if-elsevoid defend() {if (currentForm == "基础形态") {cout << "基础形态:展开普通护盾,减少30%伤害" << endl;} else if (currentForm == "闪耀形态") {cout << "闪耀形态:展开闪耀护盾,减少80%伤害(不消耗能量)" << endl;} else if (currentForm == "暗黑形态") {if (energy >= 10) {cout << "暗黑形态:展开暗黑屏障,完全抵挡一次攻击!消耗10能量" << endl;energy -= 10;} else {cout << "能量不足,无法展开暗黑屏障!" << endl;}}cout << "当前剩余能量:" << energy << "\n" << endl;}
};// 测试:形态切换与技能调用
int main() {UltramanTiga tiga;// 基础形态攻击、防御tiga.attack();  // 基础攻击tiga.defend();  // 基础防御// 切换闪耀形态tiga.switchForm("闪耀形态");tiga.attack();  // 闪耀攻击(消耗50能量)tiga.attack();  // 再次攻击(能量只剩50,刚好够)tiga.attack();  // 第三次攻击(能量为0,不足)// 新增“空中形态”?要改attack()、defend()里的if-else,太麻烦!return 0;
}

代码臃肿,攻击、防御方法里全是形态判断,新增一个形态,要改所有技能方法的if-else;行为与状态耦合:形态的行为(攻击、防御)和迪迦类紧绑,无法单独复用;维护困难,改某个形态的能量消耗,要在attack()里找对应分支,一旦分支足够多,那想找到对应分支可得遭老罪了。

状态模式的破局思路:把形态封装成状态对象。将对象的状态封装成独立的状态类,每个状态类负责实现该状态下的行为(攻击、防御);环境类(迪迦)只持有当前状态,委托状态对象处理具体行为,自己不做形态判断。

就像迪迦每次切换形态,都会切换一个战斗顾问,基础形态的顾问负责基础攻击防御,闪耀形态的顾问负责闪耀技能,迪迦只需说我要攻击/防御,顾问会自动按当前形态的规则执行。

// 迪迦类(环境类):持有当前状态,委托行为
class UltramanTiga;// 抽象状态类:所有形态的通用接口
class State {
public:virtual void attack(UltramanTiga& tiga) = 0; // 攻击行为virtual void defend(UltramanTiga& tiga) = 0; // 防御行为virtual string name() = 0; // 形态名称virtual ~State() = default;
};// 迪迦类:环境类,管理状态和能量
class UltramanTiga {
private:State* currentState; // 当前形态(状态对象)int energy;         // 能量值public:// 初始化:默认基础形态,能量100UltramanTiga() : energy(100) {currentState = new BasicState();cout << "初始化:" << currentState->name() << ",能量" << energy << "\n";}// 切换形态(核心:更换状态对象)void switchState(State* newState) {delete currentState;currentState = newState;cout << "\n切换到" << currentState->name() << ",当前能量" << energy << "\n";}// 攻击:委托给当前状态void attack() {currentState->attack(*this);cout << "剩余能量:" << energy << "\n";}// 防御:委托给当前状态void defend() {currentState->defend(*this);cout << "剩余能量:" << energy << "\n";}// 能量操作(给状态类用)int getEnergy() { return energy; }void useEnergy(int cost) { energy -= cost; }~UltramanTiga() { delete currentState; }
};// 具体状态1:基础形态
class BasicState : public State {
public:void attack(UltramanTiga& tiga) override {if (tiga.getEnergy() >= 20) {cout << "[基础形态] 哉佩利敖光线!消耗20能量";tiga.useEnergy(20);} else {cout << "[基础形态] 能量不足,无法攻击";}}void defend(UltramanTiga& tiga) override {cout << "[基础形态] 普通护盾,减伤30%";}string name() override { return "基础形态"; }
};// 具体状态2:闪耀形态
class ShinyState : public State {
public:void attack(UltramanTiga& tiga) override {if (tiga.getEnergy() >= 50) {cout << "[闪耀形态] 闪耀光线!消耗50能量";tiga.useEnergy(50);} else {cout << "[闪耀形态] 能量不足,无法攻击";}}void defend(UltramanTiga& tiga) override {cout << "[闪耀形态] 闪耀护盾,减伤80%";}string name() override { return "闪耀形态"; }
};// 具体状态3:暗黑形态
class DarkState : public State {
public:void attack(UltramanTiga& tiga) override {if (tiga.getEnergy() >= 15) {cout << "[暗黑形态] 暗黑爪击!消耗15能量";tiga.useEnergy(15);} else {cout << "[暗黑形态] 能量不足,无法攻击";}}void defend(UltramanTiga& tiga) override {if (tiga.getEnergy() >= 10) {cout << "[暗黑形态] 暗黑屏障!消耗10能量";tiga.useEnergy(10);} else {cout << "[暗黑形态] 能量不足,无法防御";}}string name() override { return "暗黑形态"; }
};// 测试
int main() {UltramanTiga tiga;// 基础形态战斗cout << "\n=== 基础形态 ===";tiga.attack();  // 基础攻击tiga.defend();  // 基础防御// 切换闪耀形态tiga.switchState(new ShinyState());cout << "=== 闪耀形态 ===";tiga.attack();  // 闪耀攻击tiga.defend();  // 闪耀防御// 切换暗黑形态tiga.switchState(new DarkState());cout << "=== 暗黑形态 ===";tiga.attack();  // 暗黑攻击tiga.defend();  // 暗黑防御return 0;
}

注意:状态模式和策略模式长得像,但核心不同,策略模式是选工具干同一件事(比如选光线或格斗攻击),状态模式是状态变了,做事方式也变了(比如闪耀形态的攻击、防御都和基础形态不同)。

4.9 访问者模式

光之国的战斗系统里,已经有三类核心对象:奥特战士(迪迦、赛罗)、怪兽(哥莫拉、杰顿)、装备(光剑、护盾)。这些对象的类早就上线运行,架构师明确要求不能随便改这些核心类,万一引入 bug,整个战斗系统都要出问题。

但最近指挥部提了两个新需求:1)生成所有对象的战斗报告;2)统计所有对象的总战力贡献。

但是直接在奥特战士、怪兽类中增加生成报告和统计贡献类,既违规又不合理。

访问者模式正是解决这个矛盾的关键:把新行为(生成报告、统计战力)封装成访问者,让旧对象通过简单的接待方法配合访问者,不用改核心逻辑就能扩展新功能。

假设光之国已有的核心类是这样的(架构师禁止修改):

// 奥特战士类
class Ultraman {
private:string name;  // 名字int power;    // 战力
public:Ultraman(string n, int p) : name(n), power(p) {}// 核心方法:战斗(已有逻辑,不能动)void fight() { cout << name << "发起攻击!战力:" << power << endl; }string getName() const { return name; }int getPower() const { return power; }
};// 怪兽类(已上线,禁止修改)
class Monster {
private:string name;  // 名字int hp;       // 血量(战力相关)
public:Monster(string n, int h) : name(n), hp(h) {}// 核心方法:破坏(已有逻辑,不能动)void destroy() { cout << name << "破坏城市!血量:" << hp << endl; }string getName() const { return name; }int getHp() const { return hp; }
};// 装备类(已上线,禁止修改)
class Equipment {
private:string name;  // 名字int boost;    // 战力加成
public:Equipment(string n, int b) : name(n), boost(b) {}// 核心方法:生效(已有逻辑,不能动)void takeEffect() { cout << name << "生效!战力加成:" << boost << endl; }string getName() const { return name; }int getBoost() const { return boost; }
};

新需求(生成报告、统计战力)需要访问这些类的私有属性(名字、战力、血量),但又不能直接在类里加新方法;并且新需求可能还会增加,总不能每次都改核心类。

因此,我们定义访问者接口(新行为的抽象),每个方法对应一种核心对象,负责处理该对象的新行为,向每个类中增加accept方法,用来接待访问者。

#include <iostream>
#include <string>
#include <vector>
using namespace std;// 1. 前向声明:告诉编译器这些类存在(解决IVisitor不认识类的问题)
class Ultraman;
class Monster;
class Equipment;// 2. 统一基类:所有可被访问的对象都继承它(避免void*,支持动态类型转换)
class Visitable {
public:// 纯虚函数:所有子类必须实现accept(接待访问者)virtual void accept(class IVisitor& visitor) = 0;// 虚析构:避免基类指针析构时内存泄漏virtual ~Visitable() = default;
};// 3. 访问者接口(IVisitor):现在能识别前向声明的类了
class IVisitor {
public:virtual void visitUltraman(const Ultraman& ultraman) = 0;virtual void visitMonster(const Monster& monster) = 0;virtual void visitEquipment(const Equipment& equipment) = 0;virtual ~IVisitor() = default;
};// 4. 奥特战士类(继承Visitable,实现accept)
class Ultraman : public Visitable {
private:string name;int power;
public:Ultraman(string n, int p) : name(n), power(p) {}void fight() { cout << name << "发起攻击!战力:" << power << endl; }string getName() const { return name; }int getPower() const { return power; }// 实现基类的accept方法(接待访问者)void accept(IVisitor& visitor) override {visitor.visitUltraman(*this);}
};// 5. 怪兽类(继承Visitable,实现accept)
class Monster : public Visitable {
private:string name;int hp;
public:Monster(string n, int h) : name(n), hp(h) {}void destroy() { cout << name << "破坏城市!血量:" << hp << endl; }string getName() const { return name; }int getHp() const { return hp; }void accept(IVisitor& visitor) override {visitor.visitMonster(*this);}
};// 6. 装备类(继承Visitable,实现accept)
class Equipment : public Visitable {
private:string name;int boost;
public:Equipment(string n, int b) : name(n), boost(b) {}void takeEffect() { cout << name << "生效!战力加成:" << boost << endl; }string getName() const { return name; }int getBoost() const { return boost; }void accept(IVisitor& visitor) override {visitor.visitEquipment(*this);}
};// 7. 具体访问者1:战斗报告访问者
class ReportVisitor : public IVisitor {
private:string report;
public:void visitUltraman(const Ultraman& ultraman) override {report += "【奥特战士】名字:" + ultraman.getName() + ",战力:" + to_string(ultraman.getPower()) + "\n";}void visitMonster(const Monster& monster) override {report += "【怪兽】名字:" + monster.getName() + ",血量:" + to_string(monster.getHp()) + "\n";}void visitEquipment(const Equipment& equipment) override {report += "【装备】名字:" + equipment.getName() + ",战力加成:" + to_string(equipment.getBoost()) + "\n";}string getReport() const {return "=== 光之国战斗报告 ===\n" + report;}
};// 8. 具体访问者2:战力统计访问者
class PowerVisitor : public IVisitor {
private:int totalPower = 0;
public:void visitUltraman(const Ultraman& ultraman) override {totalPower += ultraman.getPower();cout << "统计:" << ultraman.getName() << "贡献战力" << ultraman.getPower() << endl;}void visitMonster(const Monster& monster) override {int threat = monster.getHp() / 2;totalPower -= threat;cout << "统计:" << monster.getName() << "扣减战力" << threat << endl;}void visitEquipment(const Equipment& equipment) override {totalPower += equipment.getBoost();cout << "统计:" << equipment.getName() << "贡献加成" << equipment.getBoost() << endl;}int getTotalPower() const {return totalPower;}
};// 9. 主函数(用Visitable基类指针存储对象,解决void*转换问题)
int main() {// 创建核心对象(都是Visitable的子类)Ultraman tiga("迪迦", 5000);Ultraman zero("赛罗", 6000);Monster gomora("哥莫拉", 3000);Monster jedon("杰顿", 4000);Equipment lightSword("光剑", 800);Equipment shield("护盾", 500);// 用基类指针vector存储vector<Visitable*> objects = {&tiga, &zero, &gomora, &jedon, &lightSword, &shield};// 测试1:生成战斗报告ReportVisitor reportVisitor;cout << "=== 生成战斗报告 ===" << endl;for (auto obj : objects) {// 直接调用accept,无需判断类型!(双分派的核心:对象自己知道怎么接待访问者)obj->accept(reportVisitor);}cout << reportVisitor.getReport() << endl << endl;// 测试2:统计总战力PowerVisitor powerVisitor;cout << "=== 统计总战力 ===" << endl;for (auto obj : objects) {obj->accept(powerVisitor);}cout << "光之国总战力贡献:" << powerVisitor.getTotalPower() << endl;return 0;
}

如果你看懂了,或许会有疑问,这不最终还是修改了节点类?是的,但毕竟改动很小,且使得我们能够在后续进一步添加行为时无需再次修改代码。

现在,如果我们抽取出所有访问者的通用接口,所有已有的节点都能与我们在程序中引入的任何访问者交互。如果需要引入与节点相关的某个行为,你只需要实现一个新的访问者类即可。

4.10 中介者模式

在光之国,奥特曼接任务前都要通过任务申请面板操作。这个面板里有三个关键功能组件:

  • 任务选择器:让奥特曼挑选要执行的任务;
  • 能量校验器:录入奥特曼当前的能量值,判断是否满足任务需求;
  • 提交按钮:确认信息后点击提交,完成任务申请;

最开始,这三个组件的交互方式很直接:提交按钮被点击后,要自己去调用任务选择器的接口获取选中的任务,再调用能量校验器的接口检查能量是否足够。要是后续想加功能,就得修改提交按钮的代码,给它新增队员检查器的依赖,久而久之,组件间的关联像一团乱麻,改一处就得动好几处,维护起来特别费劲。

// 任务选择器:负责选择任务
class TaskSelector {
private:string selectedTask;
public:void chooseTask(string task) { selectedTask = task; }// 提交按钮需要直接调用这个方法获取任务string getTask() const { return selectedTask.empty() ? "未选择" : selectedTask; }
};// 能量校验器:负责录入和检查能量
class EnergyChecker {
private:int energy;
public:void inputEnergy(int e) { energy = e; }// 提交按钮需要直接调用这个方法检查能量bool isEnough(int need) const { return energy >= need; }int getEnergy() const { return energy; }
};// 提交按钮:直接依赖另外两个组件,逻辑臃肿
class SubmitButton {
private:TaskSelector& selector;  // 直接持有任务选择器引用EnergyChecker& checker;  // 直接持有能量校验器引用
public:// 构造时必须传入两个组件,缺一不可SubmitButton(TaskSelector& s, EnergyChecker& c) : selector(s), checker(c) {}void onClick() {cout << "提交按钮被点击..." << endl;// 自己调用组件方法做校验,逻辑全堆在这里string task = selector.getTask();if (task == "未选择") {cout << "错误:请先选择任务!" << endl;return;}if (!checker.isEnough(500)) {  // 假设任务至少需要500能量cout << "错误:能量" << checker.getEnergy() << "不足,无法执行" << task << "!" << endl;return;}cout << "任务申请成功!任务:" << task << ",能量:" << checker.getEnergy() << endl;}
};// 测试:新增功能就得修改提交按钮
int main() {TaskSelector selector;EnergyChecker checker;SubmitButton btn(selector, checker);  // 按钮必须绑定两个组件// 模拟操作:选任务、输能量、点按钮selector.chooseTask("消灭哥莫拉");checker.inputEnergy(400);  // 能量不足btn.onClick();// 若要加“队员数量检查”,需给SubmitButton加新依赖、改onClick()——麻烦!return 0;
}

运行结果会显示能量不足的错误,但核心问题很明显,提交按钮离了任务选择器和能量校验器就没法工作,且新增任何检查项,都要修改提交按钮的代码,校验逻辑混在按钮里,组件职责不清晰。

这时候,中介者模式就能派上用场,我们让任务申请面板本身担任中介者,所有组件不再直接互动,而是通过面板来传递信息、协调逻辑。组件只需要做好自己的本职工作,遇到需要配合的情况,告诉面板 我这边发生了什么,剩下的协调工作全交给面板处理。

#include <iostream>
#include <string>
#include <unordered_map>
#include <sstream>
using namespace std;// 辅助函数:int转string(应该能兼容所有C++版本)
string intToStr(int num) {ostringstream oss;oss << num;return oss.str();
}// 前向声明
class Mediator;// 组件基类
class Component {
protected:Mediator* mediator;string compName;public:Component(Mediator* m, string name) : mediator(m), compName(name) {}virtual void notify(string event, string data = "") = 0; // 核心方法:向中介者发送事件通知string getName() const { return compName; }              // 获取组件名称(给中介者用)virtual ~Component() = default;  // 析构函数
};// 中介者基类
class Mediator {
public:virtual void registerComponent(Component* comp) = 0;  // 注册组件:中介者需要管理所有组件virtual void handleNotification(Component* sender, string event, string data = "") = 0;  // 处理组件通知:收到组件的事件后,协调其他组件工作virtual ~Mediator() = default;
};// 任务选择器
class TaskSelector : public Component {
private:string selectedTask;public:TaskSelector(Mediator* m) : Component(m, "TaskSelector") {}     // 构造时绑定中介者和组件名// 本职工作:选择任务void chooseTask(string task) {selectedTask = task;notify("TaskSelected", task);  // 向中介者发送通知:“任务已选择,任务是XXX”}// 实现通知方法:将事件传给中介者void notify(string event, string data) override {mediator->handleNotification(this, event, data);  }// 获取选中的任务,给中介者用的方法string getSelectedTask() const {return selectedTask.empty() ? "未选择" : selectedTask;}
};// 能量校验器
class EnergyChecker : public Component {
private:int energy = 0;public:EnergyChecker(Mediator* m) : Component(m, "EnergyChecker") {}// 本职工作:录入能量void inputEnergy(int e) {energy = e;notify("EnergyInput", intToStr(e));  // 向中介者发送通知:“能量已录入,数值是XXX”}void notify(string event, string data) override {mediator->handleNotification(this, event, data);}// 给中介者用的方法:检查能量是否足够bool isEnergyEnough(int need) const {return energy >= need;}// 给中介者用的方法:获取录入的能量int getEnergy() const {return energy;}
};// 提交按钮
class SubmitButton : public Component {
public:SubmitButton(Mediator* m) : Component(m, "SubmitButton") {}// 本职工作:响应点击void onClick() {notify("BtnClicked", "");}void notify(string event, string data = "") override {mediator->handleNotification(this, event, data);}
};// 任务申请面板(中介者)
class TaskApplyPanel : public Mediator {
private:unordered_map<string, Component*> components;  // 用哈希表存储组件,方便快速查找// 缓存常用组件的指针(避免频繁类型转换)TaskSelector* taskSelector = nullptr;EnergyChecker* energyChecker = nullptr;public:// 注册组件:将组件加入管理,并缓存特定组件指针void registerComponent(Component* comp) override {string compName = comp->getName();components[compName] = comp;cout << "任务面板:已注册组件【" << compName << "】" << endl;// 缓存任务选择器和能量校验器的指针if (compName == "TaskSelector") {taskSelector = dynamic_cast<TaskSelector*>(comp);} else if (compName == "EnergyChecker") {energyChecker = dynamic_cast<EnergyChecker*>(comp);}}void handleNotification(Component* sender, string event, string data) override {string senderName = sender->getName();cout << "\n任务面板:收到【" << senderName << "】的事件——" << event << "(数据:" << data << ")" << endl;// 1. 若提交按钮被点击:协调任务选择器和能量校验器做校验if (senderName == "SubmitButton" && event == "BtnClicked") {// 先检查组件是否齐全if (!taskSelector || !energyChecker) {cout << "任务面板:错误!组件未齐全,无法提交" << endl;return;}// 调用组件的方法获取数据(仅中介者能调用)string task = taskSelector->getSelectedTask();int needEnergy = 500;// 校验逻辑(集中在中介者,组件不用管)if (task == "未选择") {cout << "任务面板:错误!请先选择任务" << endl;} else if (!energyChecker->isEnergyEnough(needEnergy)) {cout << "任务面板:错误!能量" << energyChecker->getEnergy() << "不足(需至少" << needEnergy << "),无法执行【" << task << "】" << endl;} else {cout << "任务面板:申请成功!任务【" << task << "】,能量【" << energyChecker->getEnergy() << "】" << endl;}}// 2. 若任务被选择/能量被录入:可添加额外提示(比如更新面板状态)if ((senderName == "TaskSelector" && event == "TaskSelected") || (senderName == "EnergyChecker" && event == "EnergyInput")) {cout << "任务面板:提示——【" << senderName << "】已更新为:" << data << endl;}}
};int main() {// 1. 创建中介者:任务申请面板Mediator* taskPanel = new TaskApplyPanel();// 2. 创建组件:每个组件都绑定中介者(不依赖其他组件)Component* selector = new TaskSelector(taskPanel);Component* energyChecker = new EnergyChecker(taskPanel);Component* submitBtn = new SubmitButton(taskPanel);// 3. 注册组件到中介者taskPanel->registerComponent(selector);taskPanel->registerComponent(energyChecker);taskPanel->registerComponent(submitBtn);// 4. 模拟用户操作:选任务、输能量、点按钮cout << "\n=== 开始任务申请 ===" << endl;dynamic_cast<TaskSelector*>(selector)->chooseTask("消灭哥莫拉");dynamic_cast<EnergyChecker*>(energyChecker)->inputEnergy(400);dynamic_cast<SubmitButton*>(submitBtn)->onClick();cout << "\n=== 修正能量后重新申请 ===" << endl;dynamic_cast<EnergyChecker*>(energyChecker)->inputEnergy(600);dynamic_cast<SubmitButton*>(submitBtn)->onClick();delete submitBtn;delete energyChecker;delete selector;delete taskPanel;return 0;
}

中介者模式就像光之国的任务申请专员:每个组件(任务选择器、能量校验器)只把自己的操作结果告诉专员,专员负责核对信息、检查条件,最后告诉提交按钮能不能提交,组件不用互相打听,专员统一协调,整个流程既清晰又灵活!

这里说一些课外内容:可以发现我用了以下内容进行组件创建:

Component* selector = new TaskSelector(taskPanel);

这就导致指针selector只知道自己指向的是Component类型的对象(基类视角),而Component基类里没有chooseTask方法(基类只有notify等通用方法),因此无法直接调用chooseTask—— 必须通过dynamic_cast(安全向下转型)把基类指针转成派生类指针,才能访问派生类的专属方法:

dynamic_cast<TaskSelector*>(selector)->chooseTask("消灭哥莫拉");

为什么呢?因为我在中介者类中使用哈希表存储组件:

unordered_map<string, Component*> components;

因为任务面板需要管理多种不同的组件(TaskSelector任务选择器、EnergyChecker能量校验器、SubmitButton提交按钮),这些组件都是Component的派生类,所以只能用基类指针Component,如果用派生类指针,需要为每种组件定义一个容器(vector<TaskSelector>、vector<EnergyChecker>...),无法统一管理,代码会变得极其臃肿。这一点在访问者模式代码main()函数中其实也有所体现:

vector<Visitable*> objects = {&tiga, &zero, &gomora, &jedon, &lightSword, &shield};

由于我们后面对objects的使用局限于accept方法,这个方法;基类有声明且子类都有实现,所以不需要dynamic_cast关键字。

4.11 解释器模式

光之国的战士们经常需要通过语音指令触发技能,但指令格式不固定(比如 “迪迦 使用 光线 攻击 哥莫拉”“赛罗 用 飞踢 打 杰顿”)。如果直接硬编码处理每一种指令,新增指令(如 “泽塔 释放 流星技术 瞄准 雷德王”)时就要改大量代码:

// 又是熟悉的if-else结构
void executeCommand(string command) {if (command == "迪迦 使用 光线 攻击 哥莫拉") {cout << "执行:迪迦发射光线攻击哥莫拉!" << endl;} else if (command == "赛罗 用 飞踢 打 杰顿") {cout << "执行:赛罗用飞踢打杰顿!" << endl;} else if (command == "泽塔 释放 流星技术 瞄准 雷德王") {// 新增指令要加新分支,代码越改越乱cout << "执行:泽塔释放流星技术瞄准雷德王!" << endl;} else {cout << "未知指令:" << command << endl;}
}int main() {executeCommand("迪迦 使用 光线 攻击 哥莫拉");executeCommand("赛罗 用 飞踢 打 杰顿");return 0;
}

而解释器模式能通过定义指令语法,让新增指令只需扩展解析规则,不用修改核心逻辑。把指令语法”和解析执行分离,先定义指令的语法规则(比如 “奥特曼名 + 动作词 + 技能名 + 目标词 + 目标名),再构建解释器按规则解析指令,最后执行动作。

#include <iostream>
#include <string>
using namespace std;// 上下文(Context):存储指令解析的临时数据
class CommandContext {
private:string currentUltraman;  // 解析出的奥特曼名string currentSkill;     // 解析出的技能名string currentTarget;    // 解析出的目标名
public:// 设置/获取奥特曼名void setUltraman(string name) { currentUltraman = name; }string getUltraman() const { return currentUltraman; }// 设置/获取技能名void setSkill(string skill) { currentSkill = skill; }string getSkill() const { return currentSkill; }// 设置/获取目标名void setTarget(string target) { currentTarget = target; }string getTarget() const { return currentTarget; }
};// 抽象表达式(Abstract Expression):技能指令表达式接口
class CommandExpression {
public:// 核心方法:解析指令,操作上下文(返回是否解析成功)virtual bool interpret(CommandContext& context, const string& token) = 0;virtual ~CommandExpression() = default;
};// 终结符表达式1:解析奥特曼名(如“迪迦”“赛罗”)
class UltramanExpression : public CommandExpression {
public:bool interpret(CommandContext& context, const string& token) override {// 判断token是否是已知的奥特曼名if (token == "迪迦" || token == "赛罗" || token == "泽塔") {context.setUltraman(token);  // 存入上下文cout << "解析出奥特曼:" << token << endl;return true;}return false;  // 不是奥特曼名,解析失败}
};// 终结符表达式2:解析技能名(如“光线”“飞踢”“流星技术”)
class SkillExpression : public CommandExpression {
public:bool interpret(CommandContext& context, const string& token) override {if (token == "光线" || token == "飞踢" || token == "流星技术") {context.setSkill(token);  // 存入上下文cout << "解析出技能:" << token << endl;return true;}return false;}
};// 终结符表达式3:解析目标名(如“哥莫拉”“杰顿”“雷德王”)
class TargetExpression : public CommandExpression {
public:bool interpret(CommandContext& context, const string& token) override {if (token == "哥莫拉" || token == "杰顿" || token == "雷德王") {context.setTarget(token);  // 存入上下文cout << "解析出目标:" << token << endl;return true;}return false;}
};// 非终结符表达式1:解析“动作词+技能名”规则(如“使用 光线”“释放 流星技术”)
class UseSkillExpression : public CommandExpression {
private:SkillExpression skillExpr;  // 依赖技能表达式(组合终结符表达式)
public:bool interpret(CommandContext& context, const string& token) override {// 动作词(使用/发射/释放)触发后续技能解析if (token == "使用" || token == "发射" || token == "释放") {cout << "解析出动作:" << token << "(等待技能名)" << endl;// 这里简化处理:假设下一个token是技能名,实际场景需结合语法树顺序return true;  // 动作词解析成功,后续解析技能名}// 如果不是动作词,尝试解析为技能名(由skillExpr处理)return skillExpr.interpret(context, token);}
};// 非终结符表达式2:解析“目标词+目标名”规则(如“攻击 哥莫拉”“瞄准 雷德王”)
class AttackTargetExpression : public CommandExpression {
private:TargetExpression targetExpr;  // 依赖目标表达式
public:bool interpret(CommandContext& context, const string& token) override {// 目标词(攻击/打/瞄准)触发后续目标解析if (token == "攻击" || token == "打" || token == "瞄准") {cout << "解析出目标动作:" << token << "(等待目标名)" << endl;return true;}// 不是目标词,尝试解析为目标名return targetExpr.interpret(context, token);}
};// 客户端:构建语法树,解析指令并执行
int main() {// 1. 创建各种表达式(语法规则的组成部分)UltramanExpression ultramanExpr;    // 解析奥特曼名UseSkillExpression useSkillExpr;    // 解析“使用技能”规则AttackTargetExpression attackExpr;  // 解析“攻击目标”规则// 2. 待解析的指令(可替换为其他符合语法的指令)string command = "迪迦 使用 光线 攻击 哥莫拉";// 拆分指令为token(词元):按空格分割string tokens[] = {"迪迦", "使用", "光线", "攻击", "哥莫拉"};int tokenCount = sizeof(tokens) / sizeof(tokens[0]);// 3. 创建上下文(存储解析结果)CommandContext context;// 4. 按语法顺序解析每个token(语法树执行顺序)cout << "开始解析指令:" << command << endl;for (int i = 0; i < tokenCount; i++) {string token = tokens[i];cout << "\n正在解析词元:" << token << endl;// 按语法规则选择表达式解析:// 第1个token:奥特曼名(用UltramanExpression解析)if (i == 0) {if (!ultramanExpr.interpret(context, token)) {cout << "解析失败:" << token << "不是已知奥特曼" << endl;return 1;}}// 第2-3个token:动作词+技能名(用UseSkillExpression解析)else if (i == 1 || i == 2) {if (!useSkillExpr.interpret(context, token)) {cout << "解析失败:" << token << "不是有效动作/技能" << endl;return 1;}}// 第4-5个token:目标词+目标名(用AttackTargetExpression解析)else if (i == 3 || i == 4) {if (!attackExpr.interpret(context, token)) {cout << "解析失败:" << token << "不是有效目标动作/目标" << endl;return 1;}}}// 5. 解析完成,执行最终动作(从上下文获取数据)cout << "\n=== 指令解析完成,执行动作 ===" << endl;cout << context.getUltraman() << " " << "使用" << " " << context.getSkill() << " " << "攻击" << " " << context.getTarget() << "!" << endl;// 测试新增指令:赛罗 用 飞踢 打 杰顿(无需修改表达式代码,只需改token)cout << "\n=== 测试新增指令 ===" << endl;string newCommand = "赛罗 用 飞踢 打 杰顿";string newTokens[] = {"赛罗", "用", "飞踢", "打", "杰顿"};int newTokenCount = sizeof(newTokens) / sizeof(newTokens[0]);CommandContext newContext;cout << "开始解析指令:" << newCommand << endl;for (int i = 0; i < newTokenCount; i++) {string token = newTokens[i];cout << "\n正在解析词元:" << token << endl;if (i == 0) {ultramanExpr.interpret(newContext, token);} else if (i == 1 || i == 2) {useSkillExpr.interpret(newContext, token);} else if (i == 3 || i == 4) {attackExpr.interpret(newContext, token);}}cout << "\n=== 执行新增指令动作 ===" << endl;cout << newContext.getUltraman() << " " << "用" << " " << newContext.getSkill() << " " << "打" << " " << newContext.getTarget() << "!" << endl;return 0;
}

这B玩意儿看起来似乎比if-else更复杂了,是的,简单场景下是这样的,但如果指令规则变得复杂时,比如

  • 参数化:迪迦 使用 光线 (威力 80%) 攻击 哥莫拉”
  • 组合指令:“赛罗 先 飞踢 再 光线 打 杰顿”
  • 条件指令:“如果 杰顿 血量 < 50%,泽塔 释放 流星技术”)

此时硬编码if-else会嵌套几十上百层,并且新增规则要改大量代码,简直灾难。而解释器模式通过表达式拆分规则,能轻松应对:

新增参数解析?加一个表达式,新增组合指令?我再加一个SequenceExpression表达式。并且完全不用修改现有代码,符合开放封闭原则

4.12 行为型设计模式总结

行为型设计模式本质都是在做解耦,降低对象之间的依赖,让系统更灵活、更好维护。

模式名称 核心痛点(解决什么问题) 适用场景
策略模式 一个对象需要多种行为,硬编码切换(if-else堆砌)麻烦 多种算法 / 行为可选,需动态切换(如支付方式、排序算法)
状态模式 对象状态多,状态切换导致行为逻辑混乱(状态决定行为) 对象状态决定行为,状态切换频繁(如订单状态、游戏角色形态)
命令模式 请求的发送者(如按钮)和执行者(如技能)直接耦合 需解耦请求发送与执行,支持撤销 / 日志 / 队列(如遥控器、任务调度)
模板方法模式 流程固定但部分步骤实现不同,重复写流程代码 流程标准化,部分步骤个性化(如报表生成、测试流程)
责任链模式 多步校验 / 处理,步骤增减频繁,硬编码顺序难维护 需要多对象依次处理请求,动态调整处理顺序(如过滤器、多级审批)
访问者模式 核心类(如数据结构)稳定,但需频繁新增功能,改核心类风险高 核心类不易修改,需扩展新操作(如报表生成、数据导出)
观察者模式 一个对象变化,多个对象需同步响应,耦合紧密 一对多依赖,需联动更新(如消息通知、数据联动展示)
中介者模式 多组件(如按钮 / 输入框 / 校验器)互相依赖,关系成 “蜘蛛网” 组件多且交互频繁,需减少直接依赖(如 UI 组件协调、聊天系统)
解释器模式 需要解析执行特定语法的指令(如自定义规则、表达式) 有简单语法规则,需解释执行指令(如公式计算、规则引擎)
备忘录模式 需要保存 / 恢复对象状态,又不想暴露内部细节 需撤销操作,保存状态快照(如游戏存档、编辑撤销)
迭代器模式 遍历不同集合(列表 / 栈 / 树),方式不统一,依赖集合内部结构 需要统一遍历不同结构的集合(如容器遍历、数据迭代)
责任链模式 多步校验 / 处理,步骤增减频繁,硬编码顺序难维护 需要多对象依次处理请求,动态调整处理顺序(如过滤器、多级审批)
访问者模式 核心类(如数据结构)稳定,但需频繁新增功能,改核心类风险高 核心类不易修改,需扩展新操作(如报表生成、数据导出)
观察者模式 一个对象变化,多个对象需同步响应,耦合紧密 一对多依赖,需联动更新(如消息通知、数据联动展示)
中介者模式 多组件(如按钮 / 输入框 / 校验器)互相依赖,关系成 “蜘蛛网” 组件多且交互频繁,需减少直接依赖(如 UI 组件协调、聊天系统)
解释器模式 需要解析执行特定语法的指令(如自定义规则、表达式) 有简单语法规则,需解释执行指令(如公式计算、规则引擎)
备忘录模式 需要保存 / 恢复对象状态,又不想暴露内部细节 需撤销操作,保存状态快照(如游戏存档、编辑撤销)
迭代器模式 遍历不同集合(列表 / 栈 / 树),方式不统一,依赖集合内部结构 需要统一遍历不同结构的集合(如容器遍历、数据迭代)
http://www.hskmm.com/?act=detail&tid=38352

相关文章:

  • 深度剖析OpenHarmony AI Engine:开发板端侧大模型推理插件机制全链路拆解 - 实践
  • Linux下的拼音输入法 (3)
  • P2606 [ZJOI2010] 排列计数 分析
  • 分治算法乱讲
  • 电动三轮车后桥改造添加动能回收实现无限续航的可能。
  • Claude Code配置记录
  • 视频融合平台EasyCVR在智慧工地中的应用:构建安全、智能、高效的“云上工地” - 实践
  • 股票操作统计分析报告 - 2025年10月24日
  • [HZOI] CSP-S模拟37 赛后总结
  • 24
  • 数字人:数字人公司排行榜及技术深度剖析
  • 【同余最短路】学习笔记
  • 数字人:数字人公司深度解析与未来展望
  • CSP/NOIP 复习:单调栈
  • 算法分析--生成排列
  • 三大安全认证授权协议深度对比:OAuth、OpenID Connect与SAML
  • 数字人公司:数字人新趋势技术驱动与市场前景解析
  • AI股票预测分析报告 - 2025年10月24日
  • 数据绑定相关概念理解
  • 数字人企业:数字人公司排行榜Top 3解析
  • (简记)(自用)线段树区间拆分时间复杂度证明
  • 数字人企业:数字人公司排行榜深度解析
  • 数字人:怎么选择数字人实力公司
  • 拉格朗日插值优化DP
  • 冬日绘板 2026 珂朵莉计划 如何获取 Token
  • 数字人企业:数字人公司技术驱动的三大标杆
  • Linux下的拼音输入法 (2)
  • 数字人平台:重点推荐优质数字人公司
  • SpringBoot整合缓存2-Redis
  • 数字人企业:推荐数字人TOP3公司