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

精读C++20设计模式——结构型设计模式:享元模式

精读C++20设计模式——结构型设计模式:享元模式

前言

​ 现在我们来仔细学习一下享元模式:Flyweight,但是我觉得好像叫 Token 或者是 Cookie更加的合适(原书并列的说,笔者认为后两个说辞我显然更加能接受和理解),它主要是尝试解决一种性能问题——我们可不可以复用一些已经有的东西呢(注意我们没有在复用抽象,而是在复用数据,更加像是期待用引用取代值)

一个例子:文本显示

​ 假设我们现在正在打开一本超级无敌大的书,大约几千万个字吧!但是我们知道,常用的汉字本身可能也就不到 4000 个。比如说,在整本书中,大量出现了“我”、“你”、“吧”等高频词汇。那问题来了:我们真的有必要在内存里反复存储一大堆一模一样的字形数据吗?显然没有必要。

​ 我们完全可以不存储字本身的完整信息,而是存储指向这个字的索引。换句话说,就是把“字”放到一个共享的字典池里,文本本身只记录“引用”。这样,当需要显示时,再根据索引去取对应的字形。这种方式就避免了冗余存储,从而极大节省了内存空间。

那为什么 ASCII 文本中我们没有这么做呢?原因在于代价问题。ASCII 编码本身只占一个字节,而指针或索引往往需要 2~4 个字节(甚至更多),这样一来就得不偿失了。换句话说,ASCII 字符已经足够小,继续压缩反而增加了额外负担。

​ 不过,事情并没有到此为止。让我们继续思考:如果不仅仅是单个字频繁出现,还有一些字的组合或搭配也非常常见,比如“你好”、“谢谢”、“没事吧”。在整本书的文本里,这些词组也会反复出现。那我们是不是也可以用同样的方法,把它们放到共享池里,然后文本只存储索引?

​ 这样一来,我们就不仅减少了对单字的重复存储,还进一步减少了对常见词组的重复存储。随着共享池的规模合理扩展,我们就能用有限的资源,存储和渲染出极其庞大的文本。这种思想,其实就是**享元模式(Flyweight Pattern)**在文本显示场景下的一个具体应用。

#include <iostream>
#include <unordered_map>
#include <memory>
#include <string>
#include <vector>// 享元对象:表示一个“字”或“词组”
class Glyph {
public:explicit Glyph(const std::string& content) : content_(content) {}void draw() const {std::cout << content_;}
private:std::string content_;
};// 享元工厂:管理所有的共享对象
class GlyphFactory {
public:std::shared_ptr<Glyph> getGlyph(const std::string& content) {auto it = pool_.find(content);if (it != pool_.end()) {return it->second; // 已存在,直接复用}auto glyph = std::make_shared<Glyph>(content);pool_[content] = glyph;return glyph;}
private:std::unordered_map<std::string, std::shared_ptr<Glyph>> pool_;
};// 客户端:文本渲染
class Document {
public:void addWord(const std::shared_ptr<Glyph>& glyph) {text_.push_back(glyph);}void render() const {for (auto& g : text_) {g->draw();}std::cout << std::endl;}
private:std::vector<std::shared_ptr<Glyph>> text_;
};int main() {GlyphFactory factory;Document doc;// 假设整本书有很多 "你", "好", "吧"auto g1 = factory.getGlyph("你");auto g2 = factory.getGlyph("好");auto g3 = factory.getGlyph("吧");// 多次重复使用,不会创建新的对象doc.addWord(g1);doc.addWord(g2);doc.addWord(g3);doc.addWord(g1);doc.addWord(g2);doc.render(); // 输出:你好吧你好
}
棋盘游戏
#include <iostream>
#include <unordered_map>
#include <memory>// 享元对象:棋子
class ChessPiece {
public:ChessPiece(const std::string& color, const std::string& type): color_(color), type_(type) {}void draw(int x, int y) const {std::cout << color_ << type_ << " 放在 (" << x << "," << y << ")\n";}
private:std::string color_; // 内部状态:颜色std::string type_;  // 内部状态:棋子类型
};// 工厂:保证相同的棋子只创建一次
class ChessFactory {
public:std::shared_ptr<ChessPiece> getChess(const std::string& color, const std::string& type) {std::string key = color + type;if (pool_.count(key)) return pool_[key];auto piece = std::make_shared<ChessPiece>(color, type);pool_[key] = piece;return piece;}
private:std::unordered_map<std::string, std::shared_ptr<ChessPiece>> pool_;
};int main() {ChessFactory factory;auto blackPawn = factory.getChess("黑", "卒");auto redPawn   = factory.getChess("红", "兵");// 外部状态:位置blackPawn->draw(2, 3);redPawn->draw(5, 6);blackPawn->draw(2, 4);
}

​ 我们可以得到输出

黑卒 放在 (2,3)
红兵 放在 (5,6)
黑卒 放在 (2,4)

即使棋盘上有 16 个卒,它们在内存中只会存在两个共享对象(黑卒、红兵)。


数据库连接池
#include <iostream>
#include <unordered_map>
#include <memory>// 享元对象:连接配置
class DBConfig {
public:DBConfig(const std::string& host, int port) : host_(host), port_(port) {}void show() const {std::cout << "DBConfig: " << host_ << ":" << port_ << "\n";}
private:std::string host_;int port_;
};// 工厂:共享相同配置
class DBConfigFactory {
public:std::shared_ptr<DBConfig> getConfig(const std::string& host, int port) {std::string key = host + ":" + std::to_string(port);if (pool_.count(key)) return pool_[key];auto cfg = std::make_shared<DBConfig>(host, port);pool_[key] = cfg;return cfg;}
private:std::unordered_map<std::string, std::shared_ptr<DBConfig>> pool_;
};int main() {DBConfigFactory factory;auto c1 = factory.getConfig("127.0.0.1", 3306);auto c2 = factory.getConfig("127.0.0.1", 3306);auto c3 = factory.getConfig("192.168.1.10", 5432);c1->show();c2->show();c3->show();
}
咋用啊?
  • 打印店字体库:一台电脑装了一份宋体字库,所有人都用这份库。没有必要为每份文档都单独保存字形。
  • 地铁 IC 卡:内部状态是固定的芯片结构,外部状态是余额、有效期等随用户变化的数据。
  • 视频游戏贴图:相同的树木、草丛、石头模型在地图里出现成百上千次,但只加载一份纹理资源,实例化时传入不同的位置和缩放参数。

总结

我们要解决什么问题?

当系统中存在大量相似对象时,内存占用可能急剧膨胀,性能下降。
但这些对象其实往往存在可复用的内部状态(如字形、纹理、连接配置),如果能共享这部分,就能显著节省资源。

享元模式要解决的核心问题就是:如何在保证功能的前提下,尽量减少内存中的冗余对象。


我们如何解决?

享元模式通过将对象的状态拆分为两类:

  1. 内部状态(Intrinsic State)
    • 对象本身不会随环境变化的、不变的、可被共享的部分。
    • 例如:汉字的字形、棋子的颜色与类型、数据库连接参数。
  2. 外部状态(Extrinsic State)
    • 与上下文相关的、每次使用时才决定的部分。
    • 例如:汉字在文档中的位置、棋子在棋盘上的坐标、连接池中的使用次数。

通过共享内部状态对象 + 外部状态由调用方提供,就能极大减少对象数量。

享元模式优缺点
优点缺点
显著减少内存占用系统复杂度增加,需要区分内外部状态
提升对象复用率不适合对象完全不同、共享价值不大的场景
便于维护共享资源池外部状态需要额外传递,增加调用方负担
http://www.dtcms.com/a/422890.html

相关文章:

  • FT8430-LRT非隔离5V100MA电源芯片,满足小家电、智能照明、MCU供电需求,替代阻容降压(典型案例,电路图)
  • [论文阅读]Benchmarking Poisoning Attacks against Retrieval-Augmented Generation
  • 精读C++20设计模式:结构型设计模式:装饰器模式
  • (数据结构)链表OJ——刷题练习
  • 怎么做网站源码温州建网站
  • 云服务器做淘客网站苏州网站制作及推广
  • hive启动报错
  • (基于江协科技)51单片机入门:6.串口
  • UE5 小知识点 —— 09 - 旋转小问题
  • Git 暂存文件警告信息:warning: LF will be replaced by CRLF in XXX.java.
  • 石狮网站建设价格万网网站根目录
  • VBA ADO使用EXCEL 8.0驱动读取 .xlsx 格式表格数据-有限支持
  • freeswitch集成离线语音识别funasr
  • 建设网站管理规定源码做网站图文教程
  • Qt 入门:构建跨平台 GUI 应用的强大框架
  • Spring WebFlux调用生成式AI提供的stream流式接口,实现返回实时对话
  • 【学习笔记】高质量数据集
  • 微美全息科学院(WIMI.US):互信息赋能运动想象脑电分类,脑机接口精度迎来突破!
  • 协议 NTP UDP 获取实时网络时间
  • 公司网站可以分两个域名做吗残疾人网站服务平台
  • spark pipeline 转换n个字段,如何对某个字段反向转换
  • 学习React-18-useCallBack
  • 长沙制作网站的公司与传统市场营销的区别与联系有哪些
  • 从语言到向量:自然语言处理核心转换技术的深度拆解与工程实践导论(自然语言处理入门必读)
  • 无人设备遥控器之无线发射接收技术篇
  • 《从数组到动态顺序表:数据结构与算法如何优化内存管理?》
  • 浏览器正能量网站2021网页设计免费模板图片
  • 花生壳内网穿透网站如何做seo优化目前最好的找工作平台
  • 1-wireshark网络安全分析——VLAN基础细节详解
  • android studio 无法运行java main()