设计模式:享元模式(Flyweight Pattern)

文章目录

    • 一、享元模式的介绍
    • 二、实例分析
    • 三、示例代码

一、享元模式的介绍

  享元模式(Flyweight Pattern) 是一种结构型设计模式。通过共享相同对象,减少内存消耗,提高性能。 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。
在这里插入图片描述

适用场景:
当系统中存在大量相似对象时,每个对象包含许多重复的状态(数据),这会浪费大量内存。
享元模式把对象的状态分为两类:

  • 内部状态(Intrinsic State):可共享的,不随环境变化的部分。
  • 外部状态(Extrinsic State):不能共享的,依赖于具体环境的部分。

通过将内部状态共享,只在需要时传入外部状态,即可避免创建大量对象。

享元模式的角色:

  • Flyweight(享元接口):定义可以共享的接口,声明接收外部状态的方法。
  • ConcreteFlyweight(具体享元类):实现享元接口,存储内部状态。
  • FlyweightFactory(享元工厂类):管理享元对象的创建和复用。
  • Client(客户端):负责传入外部状态,使用享元对象。

享元模式结构:
在这里插入图片描述

二、实例分析

问题:
假如你希望在长时间工作后放松一下, 所以开发了一款简单的游戏: 玩家们在地图上移动并相互射击。 你决定实现一个真实的粒子系统, 并将其作为游戏的特色。 大量的子弹、 导弹和爆炸弹片会在整个地图上穿行, 为玩家提供紧张刺激的游戏体验。

开发完成后, 你推送提交了最新版本的程序, 并在编译游戏后将其发送给了一个朋友进行测试。 尽管该游戏在你的电脑上完美运行, 但是你的朋友却无法长时间进行游戏: 游戏总是会在他的电脑上运行几分钟后崩溃。 在研究了几个小时的调试消息记录后, 你发现导致游戏崩溃的原因是内存容量不足。 朋友的设备性能远比不上你的电脑, 因此游戏运行在他的电脑上时很快就会出现问题。

真正的问题与粒子系统有关。 每个粒子 (一颗子弹、 一枚导弹或一块弹片) 都由包含完整数据的独立对象来表示。 当玩家在游戏中鏖战进入高潮后的某一时刻, 游戏将无法在剩余内存中载入新建粒子, 于是程序就崩溃了。

在这里插入图片描述

解决方案:
仔细观察粒子Particle类, 你可能会注意到颜色 (color) 和精灵图 (sprite)这两个成员变量所消耗的内存要比其他变量多得多。 更糟糕的是, 对于所有的粒子来说, 这两个成员变量所存储的数据几乎完全一样 (比如所有子弹的颜色和精灵图都一样)。

在这里插入图片描述
每个粒子的另一些状态 (坐标、 移动矢量和速度) 则是不同的。 因为这些成员变量的数值会不断变化。 这些数据代表粒子在存续期间不断变化的情景, 但每个粒子的颜色和精灵图则会保持不变。

对象的常量数据通常被称为内在状态, 其位于对象中, 其他对象只能读取但不能修改其数值。 而对象的其他状态常常能被其他对象 “从外部” 改变, 因此被称为外在状态。

享元模式建议不在对象中存储外在状态, 而是将其传递给依赖于它的一个特殊方法。 程序只在对象中保存内在状态, 以方便在不同情景下重用。 这些对象的区别仅在于其内在状态 (与外在状态相比, 内在状态的变体要少很多), 因此你所需的对象数量会大大削减。

在这里插入图片描述
让我们回到游戏中。 假如能从粒子类中抽出外在状态, 那么我们只需三个不同的对象 (子弹、 导弹和弹片) 就能表示游戏中的所有粒子。 你现在很可能已经猜到了, 我们将这样一个仅存储内在状态的对象称为享元。

外在状态存储:
那么外在状态会被移动到什么地方呢? 总得有类来存储它们, 对不对? 在大部分情况中, 它们会被移动到容器对象中, 也就是我们应用享元模式前的聚合对象中。

在我们的例子中, 容器对象就是主要的游戏Game对象, 其会将所有粒子存储在名为 粒子particles的成员变量中。 为了能将外在状态移动到这个类中, 你需要创建多个数组成员变量来存储每个粒子的坐标、 方向矢量和速度。 除此之外, 你还需要另一个数组来存储指向代表粒子的特定享元的引用。 这些数组必须保持同步, 这样你才能够使用同一索引来获取关于某个粒子的所有数据。

在这里插入图片描述
更优雅的解决方案是创建独立的情景类来存储外在状态和对享元对象的引用。 在该方法中, 容器类只需包含一个数组。

稍等! 这样的话情景对象数量不是会和不采用该模式时的对象数量一样多吗? 的确如此, 但这些对象要比之前小很多。 消耗内存最多的成员变量已经被移动到很少的几个享元对象中了。 现在, 一个享元大对象会被上千个情境小对象复用, 因此无需再重复存储数千个大对象的数据。

享元与不可变性:
由于享元对象可在不同的情景中使用, 你必须确保其状态不能被修改。 享元类的状态只能由构造函数的参数进行一次性初始化, 它不能对其他对象公开其设置器或公有成员变量。

享元工厂:
为了能更方便地访问各种享元, 你可以创建一个工厂方法来管理已有享元对象的缓存池。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, 如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中。

你可以选择在程序的不同地方放入该函数。 最简单的选择就是将其放置在享元容器中。 除此之外, 你还可以新建一个工厂类, 或者创建一个静态的工厂方法并将其放入实际的享元类中。

三、示例代码

示例一:
在一个文本编辑器里,每个字符都是一个对象,如果每个字符都单独创建,就会消耗大量内存。实际上,每个字符的字形(内部状态) 是可以共享的,只需要保存它们在文档中的位置、大小、颜色等外部状态 即可。

#include <iostream>
#include <map>
#include <string>
using namespace std;// 享元接口
class Character {
public:virtual void display(int size, int x, int y) = 0; // 外部状态:字体大小、位置virtual ~Character() {}
};// 具体享元类:具体字符
class ConcreteCharacter : public Character {
private:char symbol; // 内部状态:字符本身
public:ConcreteCharacter(char c) : symbol(c) {}void display(int size, int x, int y) override {cout << "字符: " << symbol<< "  字体大小: " << size<< "  位置: (" << x << "," << y << ")\n";}
};// 享元工厂类:管理共享对象
class CharacterFactory {
private:map<char, Character*> pool; // 享元池
public:~CharacterFactory() {for (auto& kv : pool) delete kv.second;}Character* getCharacter(char c) {if (pool.find(c) == pool.end()) {pool[c] = new ConcreteCharacter(c);}return pool[c];}
};// 客户端
int main() {CharacterFactory factory;Character* c1 = factory.getCharacter('A');Character* c2 = factory.getCharacter('B');Character* c3 = factory.getCharacter('A'); // 复用已有对象// 显示字符(外部状态由客户端传入)c1->display(12, 10, 20);c2->display(14, 15, 25);c3->display(16, 50, 100);return 0;
}

注意:虽然创建了三个字符,但实际上 ‘A’ 只创建了一次,第二次复用,节省了内存。

示例二:
使用用五子棋(棋盘游戏)举例实现享元模式

场景分析:

  • 在五子棋中:棋子分为黑子和白子两种。
  • 每颗棋子的颜色可以共享(内部状态 Intrinsic State)。
  • 棋子的位置 (x, y) 每次都不同(外部状态 Extrinsic State)。

因此,整个棋盘上无论下多少颗棋子,实际上只需要两个享元对象(黑子、白子)。

#include <iostream>
#include <map>
#include <string>
using namespace std;// 抽象享元类:棋子
class ChessPiece {
public:virtual void draw(int x, int y) = 0; // 外部状态:棋子位置virtual ~ChessPiece() {}
};// 具体享元类:黑子
class BlackPiece : public ChessPiece {
public:void draw(int x, int y) override {cout << "黑子落在位置 (" << x << "," << y << ")\n";}
};// 具体享元类:白子
class WhitePiece : public ChessPiece {
public:void draw(int x, int y) override {cout << "白子落在位置 (" << x << "," << y << ")\n";}
};// 享元工厂:管理棋子对象
class PieceFactory {
private:map<string, ChessPiece*> pool; // 棋子池
public:~PieceFactory() {for (auto& kv : pool) delete kv.second;}ChessPiece* getPiece(const string& color) {if (pool.find(color) == pool.end()) {if (color == "black") {pool[color] = new BlackPiece();} else if (color == "white") {pool[color] = new WhitePiece();}}return pool[color];}
};// 客户端:模拟下棋
int main() {PieceFactory factory;ChessPiece* black = factory.getPiece("black");ChessPiece* white = factory.getPiece("white");// 下棋(外部状态:位置由客户端传入)black->draw(3, 3);white->draw(4, 4);black->draw(5, 5);white->draw(6, 6);black->draw(7, 7);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97715.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Go语言入门教程】 Go语言的起源与技术特点:从诞生到现代编程利器(一)

文章目录前言1. Go语言的起源与发展2. Go语言的核心设计团队2.1 Ken Thompson&#xff08;肯汤普森&#xff09;2.2 Rob Pike&#xff08;罗布派克&#xff09;2.3 Robert Griesemer&#xff08;罗伯特格瑞泽默&#xff09;设计动机&#xff1a;解决C的痛点3. Go语言的核心特性…

rocketmq启动与测试

1.更改runserver.sh的内存大小 vi runserver.sh 2.更改 runbroker.sh内存大小 vi runbroker.sh3.设置环境变量 vi ~/.bash_profile 新增 export NAMESRV_ADDRlocalhost:98764.启动 --在bin的上一级目录启动 nohup bin/mqnamesrv & nohup bin/mqbroker &5.查看日志 le…

11.《简单的路由重分布基础知识探秘》

11_路由重分布 文章目录11_路由重分布路由重分布概述路由重分布的核心作用基础实验实验流程实验拓扑配置示例(基本操作省略)实验结论路由重分布概述 路由重分布&#xff08;又称路由引入&#xff09;是指在不同路由协议之间交换路由信息的技术。在复杂网络中&#xff0c;可能同…

C++ 左值引用与右值引用介绍

C 左值引用与右值引用详解 在 C 的类型系统中&#xff0c;引用&#xff08;reference&#xff09; 是一种为已有对象起别名的机制。在早期&#xff08;C98/03&#xff09;中&#xff0c;C 只有 左值引用&#xff08;lvalue reference&#xff09;&#xff0c;主要用于函数参数…

基于物联网设计的园林灌溉系统(华为云IOT)_274

文章目录 一、前言 1.1 项目介绍 【1】项目开发背景 【2】设计实现的功能 【3】项目硬件模块组成 【4】设计意义 【5】国内外研究现状 【6】摘要 1.2 设计思路 1.3 系统功能总结 1.4 开发工具的选择 【1】设备端开发 【2】上位机开发 1.5 参考文献 1.6 系统框架图 1.7 系统原理…

uni-app iOS 应用版本迭代与上架实践 持续更新的高效流程

很多团队在使用 uni-app 开发 iOS 应用时&#xff0c;往往能顺利完成第一次上架&#xff0c;但一到 版本更新和迭代 环节&#xff0c;就会频繁遇到瓶颈&#xff1a;证书是否能复用&#xff1f;如何快速上传&#xff1f;怎样保持节奏不被打乱&#xff1f; 本文结合实战经验&…

解决由Tomcat部署前端改成nginx部署,导致大写.JPG结尾文件无法访问问题

前言&#xff1a;因信创替代要求&#xff0c;在麒麟服务器部署新的应用。原先的架构&#xff1a;前端tomcat部署&#xff0c;源码部署java应用&#xff08;ps&#xff1a;前后端&#xff0c;文件都在同一台服务器上&#xff09;&#xff0c;前端访问后端&#xff0c;再通过后端…

【设计模式】三大原则 单一职责原则、开放-封闭原则、依赖倒转原则

系列文章目录 文章目录系列文章目录一、单一职责原则方块游戏的设计二、开放-封闭原则原则介绍何时应对变化三、依赖倒转原则依赖倒转原则介绍里氏代换原则总结一、单一职责原则 单一职责原则&#xff0c;听字面意思&#xff0c;就是说功能要单一&#xff0c;他的准确解释是&a…

(3dnr)多帧视频图像去噪 (一)

一、多帧视频图像去噪 原理当摄像机每秒捕捉的图像达到60FPS&#xff0c;除了场景切换或者一些快速运动的场 景外&#xff0c;视频信号中相邻的两帧图像内容大部分是相同的。并且视频信号中的噪 声大部分都是均值为零的随机噪声&#xff0c;因此在时间上对视频信号做帧平均&…

从静态到智能:用函数式接口替代传统工具类

在 Java 早期开发中&#xff0c;我们习惯使用**静态实用程序类&#xff08;Utility Class&#xff09;**来集中放置一些通用方法&#xff0c;例如验证、字符串处理、数学计算等。这种模式虽然简单直接&#xff0c;但在现代 Java 开发&#xff08;尤其是 Java 8 引入 Lambda 和函…

免杀伪装 ----> R3进程伪装实战(高阶) ---->培养红队免杀思路

目录 R3进程伪装(免杀技术)高阶技术说明 深入剖析Windows进程规避免杀技术 学习R3进程伪装的必备技能 R3进程伪装的核心知识点与实现步骤 核心知识点 实现步骤 免杀实现步骤 PEB与EPROCESS的深入解析 1. PEB&#xff08;进程环境块&#xff09; 2. EPROCESS 3. PEB与…

深度学习——基于卷积神经网络实现食物图像分类(数据增强)

文章目录 引言 一、项目概述 二、环境准备 三、数据预处理 3.1 数据增强与标准化 3.2 数据集准备 四、自定义数据集类 五、构建CNN模型 六、训练与评估 6.1 训练函数 6.2 评估函数 6.3 训练流程 七、关键技术与优化 八、常见问题与解决 九、完整代码 十、总结 引言 本文将详细介…

【开题答辩全过程】以 基于微信小程序的教学辅助系统 为例,包含答辩的问题和答案

个人简介一名14年经验的资深毕设内行人&#xff0c;语言擅长Java、php、微信小程序、Python、Golang、安卓Android等开发项目包括大数据、深度学习、网站、小程序、安卓、算法。平常会做一些项目定制化开发、代码讲解、答辩教学、文档编写、也懂一些降重方面的技巧。感谢大家的…

【代码解读】Deepseek_vl2中具体代码调用

【代码解读】Deepseek_vl2中具体代码调用 文章目录【代码解读】Deepseek_vl2中具体代码调用DeepseekVLV2Processor解读DeepseekVLV2ForCausalLM - 多模态模型DeepSeek-VL2 Processor的输入格式单样本格式多样本格式DeepSeek-VL2模型的输出形式总结主要输出类型&#xff1a;Deep…

Git 9 ,.git/index.lock 文件冲突问题( .git/index.lock‘: File exists. )

目录 前言 一、问题背景 1.1 问题出现场景 1.2 典型报错信息 1.3 问题影响 二、问题原因分 2.1 Git 的 index 与锁机制 2.2 主要作用 2.3 根本原因 三、解决方案 3.1 确认进程 3.2 手动删除 3.3 再次执行 四、注意事项 4.1 确保运行 4.2 问题排查 4.3 自动化解…

Proteus8 仿真教学全指南:从入门到实战的电子开发利器

在电子设计、单片机课程设计或创客实践中&#xff0c;你是否常因实物采购贵、新手怕烧板、调试排错难而头疼&#xff1f;Proteus8 作为一款 “全能型” EDA 仿真工具&#xff0c;完美解决这些痛点 —— 它集「原理图绘制 PCB 设计 虚拟仿真」于一体&#xff0c;支持 51、STM3…

系统科学:结构、功能与层级探析

摘要本文旨在系统性地梳理和辨析系统科学中的核心概念——结构、功能与层级。文章首先追溯系统思想的理论源流&#xff0c;确立其作为一种超越还原论的整体性研究范式。在此基础上&#xff0c;深度剖析系统结构的内在构成&#xff08;组分、框架、动态性&#xff09;、系统层级…

面试官问:你如何看待薪资待遇?

在面试过程中&#xff0c;“你如何看待薪资待遇&#xff1f;”这个问题&#xff0c;是很多面试官都会提出的经典问题之一。虽然表面上看起来是一个简单的提问&#xff0c;但它实则关乎候选人的职业价值观、工作态度以及对自己能力的认知。薪资是工作的重要动力之一&#xff0c;…

HarmonyOS 应用开发新范式:深入剖析 Stage 模型与 ArkUI 最佳实践

好的&#xff0c;请看这篇基于 HarmonyOS (鸿蒙) 最新技术栈的深度技术文章。 HarmonyOS 应用开发新范式&#xff1a;深入剖析 Stage 模型与 ArkUI 最佳实践 引言 随着 HarmonyOS 4、5 的持续演进和未来 6 的规划&#xff0c;其应用开发框架经历了革命性的重构。对于技术开发者…

【Python数据可视化:Matplotlib高级技巧】

Python数据可视化&#xff1a;Matplotlib高级技巧引言在数据科学和分析领域&#xff0c;数据可视化是理解和传达信息的关键工具。Python中最流行的可视化库之一就是Matplotlib。虽然初学者可以快速上手Matplotlib的基础功能&#xff0c;但掌握其高级技巧才能真正发挥这个强大库…