news 2026/5/9 23:14:02

新谈设计模式 Chapter 11 — 享元模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新谈设计模式 Chapter 11 — 享元模式

Chapter 11 — 享元模式 Flyweight

灵魂速记:共享单车——大家骑同一批车,不用每人买一辆。


秒懂类比

一个城市有 100 万人要骑自行车,你不需要造 100 万辆。造 5 万辆共享单车,谁用谁骑,用完还回去。

享元模式的核心:把大量对象中相同的部分提取出来共享,减少内存占用。


问题引入

// 灾难现场:游戏中 10000 棵树classTree{doublex_,y_;// 位置(每棵不同)std::string name_;// "Oak"(很多棵一样)std::string color_;// "green"(很多棵一样)Texture texture_;// 1MB 大小的纹理!(很多棵一样)};// 10000 棵树 × 1MB 纹理 = 10GB 内存// 💥 内存爆炸!

观察:10000 棵树中,可能只有 3 种类型(橡树、松树、桦树)。每种树的纹理是一样的,没必要存 10000 份!


内在状态 vs 外在状态

内在状态(Intrinsic)外在状态(Extrinsic)
定义可以共享的、不变的部分每个对象独有的、变化的部分
例子树的纹理、颜色、名称树的位置 x, y
存储享元对象中(共享)外部传入(不共享)

模式结构

┌────────────────┐ │ FlyweightFactory│ ← 管理共享对象的池子 ├────────────────┤ │ +getFlyweight()│ ← 有就复用,没有才创建 └───────┬────────┘ │ 返回 ┌───────┴────────┐ │ Flyweight │ ← 共享的对象(内在状态) ├────────────────┤ │ -intrinsicState│ │ +operation( │ │ extrinsicState)│ ← 外在状态从外部传入 └────────────────┘

C++ 实现

#include<iostream>#include<memory>#include<string>#include<unordered_map>#include<vector>// ========== 享元:树的类型(共享部分) ==========classTreeType{public:TreeType(std::string name,std::string color,std::string textureData):name_(std::move(name)),color_(std::move(color)),textureData_(std::move(textureData)){std::cout<<" [创建纹理] "<<name_<<" ("<<textureData_.size()<<" bytes)\n";}voiddraw(doublex,doubley)const{std::cout<<" 画 "<<name_<<"("<<color_<<") at ("<<x<<", "<<y<<")\n";}private:std::string name_;// 内在状态std::string color_;// 内在状态std::string textureData_;// 内在状态(大对象!)};// ========== 享元工厂 ==========classTreeTypeFactory{public:staticstd::shared_ptr<TreeType>getTreeType(conststd::string&name,conststd::string&color){std::string key=name+"_"+color;autoit=cache_.find(key);if(it!=cache_.end()){returnit->second;// 已有,直接复用}// 没有,创建新的(模拟大纹理数据)autotype=std::make_shared<TreeType>(name,color,std::string(1024*1024,'#'));// 模拟 1MBcache_[key]=type;returntype;}staticsize_ttypeCount(){returncache_.size();}private:// static inline:C++17 特性,允许在类内直接初始化静态成员变量。// 没有 inline 的话,你得在 .cpp 文件里写 std::unordered_map<...> TreeTypeFactory::cache_;// 有了 inline,声明和定义合一,和 Ch01 讲的 inline 变量是同一个道理。staticinlinestd::unordered_map<std::string,std::shared_ptr<TreeType>>cache_;};// ========== 具体的树(外在状态 + 享元引用) ==========classTree{public:Tree(doublex,doubley,std::shared_ptr<TreeType>type):x_(x),y_(y),type_(std::move(type)){}voiddraw()const{type_->draw(x_,y_);// 把外在状态传给享元}private:doublex_,y_;// 外在状态(每棵树不同)std::shared_ptr<TreeType>type_;// 指向共享的享元};// ========== 森林 ==========classForest{public:voidplantTree(doublex,doubley,conststd::string&name,conststd::string&color){autotype=TreeTypeFactory::getTreeType(name,color);trees_.emplace_back(x,y,type);}voiddraw()const{for(constauto&tree:trees_){tree.draw();}}size_ttreeCount()const{returntrees_.size();}private:std::vector<Tree>trees_;};intmain(){Forest forest;std::cout<<"=== 种树 ===\n";// 种 1000 棵树,但只有 3 种类型for(inti=0;i<500;++i){forest.plantTree(i*1.0,i*0.5,"Oak","green");}for(inti=0;i<300;++i){forest.plantTree(i*2.0,i*1.0,"Pine","dark_green");}for(inti=0;i<200;++i){forest.plantTree(i*0.5,i*3.0,"Birch","white");}std::cout<<"\n=== 统计 ===\n";std::cout<<"树的总数: "<<forest.treeCount()<<"\n";std::cout<<"树类型数: "<<TreeTypeFactory::typeCount()<<"\n";std::cout<<"无享元内存: "<<forest.treeCount()<<" MB (每棵 1MB 纹理)\n";std::cout<<"有享元内存: "<<TreeTypeFactory::typeCount()<<" MB (只有 "<<TreeTypeFactory::typeCount()<<" 份纹理)\n";// 节省了 997MB!}

输出:

=== 种树 === [创建纹理] Oak (1048576 bytes) [创建纹理] Pine (1048576 bytes) [创建纹理] Birch (1048576 bytes) === 统计 === 树的总数: 1000 树类型数: 3 无享元内存: 1000 MB (每棵 1MB 纹理) 有享元内存: 3 MB (只有 3 份纹理)

1000 棵树,只创建了 3 个纹理对象。内存节省 99.7%。


什么时候用?

✅ 适合❌ 别用
大量相似对象对象数量少
对象的大部分状态可以外部化每个对象都完全不同
内存是瓶颈内存充裕,不需要优化
对象状态可分为内在/外在无法拆分内在/外在状态

经验法则:对象数量在千级以上,且有大量重复数据时再考虑。


防混淆

Flyweight vs Singleton

FlyweightSingleton
实例数量多个(但共享重复的)只有一个
目的节省内存保证唯一
状态内在状态不可变可以有可变状态

一句话分清:Singleton 保证只有 1 个,Flyweight 保证共享不重复。

Flyweight vs 对象池

Flyweight对象池
共享方式同时共享(多个持有者)借出归还(一次一个使用)
可变性内在状态不可变对象可变
类比共享单车(多人同时看到车)图书馆(借走了别人就不能借)

现实中的 Flyweight

场景享元共享了什么
字符串池std::string/ Java String Pool相同的字符串值
字体渲染字符字形同一字体同一字号的字形
游戏粒子系统粒子类型纹理、物理属性
围棋/象棋程序棋子类型“黑子”"白子"的外观
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 22:47:31

Flat Surface Shader入门教程:从零开始构建你的第一个3D场景

Flat Surface Shader入门教程&#xff1a;从零开始构建你的第一个3D场景 【免费下载链接】flat-surface-shader Flat Surface Shader for rendering illuminated triangles 项目地址: https://gitcode.com/gh_mirrors/fl/flat-surface-shader Flat Surface Shader&#…

作者头像 李华
网站建设 2026/4/17 21:57:10

5个关键技术要点:全面掌握FreeMoCap开源动捕系统

5个关键技术要点&#xff1a;全面掌握FreeMoCap开源动捕系统 【免费下载链接】freemocap Free Motion Capture for Everyone &#x1f480;✨ 项目地址: https://gitcode.com/GitHub_Trending/fr/freemocap FreeMoCap是一款开源、硬件与软件无关的免费动作捕捉系统&…

作者头像 李华
网站建设 2026/4/17 7:09:16

RabbitMQ系列01 - 消息中间件与 MQ:在分布式系统里解决什么问题

消息中间件与 MQ&#xff1a;在分布式系统里解决什么问题 消息队列中间件&#xff08;MQ&#xff09;与面向消息的中间件&#xff08;MOM&#xff09;是构建分布式系统时常用的异步与解耦手段。本文说明其定义、与同步调用的差异、常见架构收益&#xff0c;以及和几款主流产品…

作者头像 李华
网站建设 2026/4/17 15:27:17

现代网页截图终极指南:轻松将DOM元素转换为高质量图像

现代网页截图终极指南&#xff1a;轻松将DOM元素转换为高质量图像 【免费下载链接】modern-screenshot &#x1f4f8; Quickly generate image from DOM node using HTML5 canvas and SVG. 项目地址: https://gitcode.com/gh_mirrors/mo/modern-screenshot 现代网页截图…

作者头像 李华