23种设计模式-结构型模式-享元

文章目录

  • 简介
  • 问题
  • 解决方案
    • 享元与不可变性
    • 享元工厂
  • 代码
  • 总结

简介

亦称:缓存、Cache、Flyweight。享元是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。

问题

假如你开发了一款简单的游戏:玩家可以在地图上移动并相互射击,比如刺激战场。你决定实现一个真实的粒子系统。大量的子弹、导弹和爆炸弹片会在整个地图上穿行,给玩家提供刺激的游戏体验。
开发完成后,你编译好打包然后发送给了一个朋友进行测试。尽管这个游戏在你的电脑上正常运行,但是你的朋友却无法长时间进行游戏:游戏总是会在他的电脑上运行几分钟后崩溃。在研究了调试信息后,你发现导致游戏崩溃的原因是电脑内存容量不足。朋友的设备性能远比不上你的电脑,所以游戏运行之后很快就会出现问题。

真正的问题其实是跟粒子系统有关。每个粒子(一颗子弹、一枚导弹或一块弹片)都由包含完整数据的独立对象来表示,如下图,每个粒子占用约21KB内存。当玩家打到高潮的时候(粒子数大约有1000000),会占用大约21GB的内存,这时内存已经不能再容纳新粒子了,于是程序就崩溃了。

在这里插入图片描述

解决方案

仔细观察粒子Par­ti­cle类,你可能会注意到颜色(color)和纹理图(sprite)这两个成员变量所消耗的内存要比其他变量多得多。而且对于所有的粒子来说,这两个成员变量所存储的数据几乎完全一样(比如所有子弹的颜色和纹理图都一样),如下图(Particle)。
在这里插入图片描述
每个粒子的另一些状态(坐标、移动矢量和速度)则是不同的(MovingParticle)。因为这些成员变量的数值会不断变化。这些数据能够代表粒子在创建之后不断变化的情景,但每个粒子的颜色和纹理图其实会保持不变。
我们知道,对象的常量数据通常被称为内在状态,它在对象里面,其他对象只能读取但不能修改他的数值。而对象的其他状态常常能被其他对象“从外部”改变,因此被称为外在状态。
享元模式建议不在对象中存储外在状态,而是把他传递给依赖于它的一个特殊方法。程序只在对象里保存内在状态,以方便在不同情景下重用。这些对象的区别仅在于他的内在状态(和外在状态相比,内在状态的变化要少很多),因此你所需的对象数量会大大削减。
让我们回到游戏的示例。假如能从粒子类里抽出外在状态(MovingParticle),那么我们只需要三个不同的对象(子弹、导弹和弹片)就能表示游戏中的所有粒子。如下图,我们把MovingParticleParticle中抽出来之后,只要一个Particle对象存储颜色和纹理图(21KB)即可, 其余外在状态都存储在MovingParticle对象中(32B),MovingParticle 对象会共享这一个Particle对象。你现在很可能已经猜到了,我们把这样一个仅存储内在状态(Particle)的对象称为享元
在这里插入图片描述

享元与不可变性

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

享元工厂

为了能更方便地访问各种享元,你可以创建一个工厂方法来管理已有享元对象的缓存池。工厂方法从客户端处接收目标享元对象的内在状态作为参数,如果它能在缓存池中找到所需享元,就把它返回给客户端;如果没有找到,它就会新建一个享元,并把他添加到缓存池里。
你可以选择在程序的不同地方放入这个函数。最简单的选择就是把他放在享元容器里。除此之外,你还可以新建一个工厂类,或者创建一个静态的工厂方法并把它放在实际的享元类里。

代码

// 粒子内在状态
final class ParticleType {private final String color;  // 不可变特征private final String texture;private final String effectType;public ParticleType(String color, String texture, String effectType) {this.color = color;this.texture = texture;this.effectType = effectType;}public void render(String position, double velocity) {System.out.printf("绘制%s特效: 位置%s | 速度%.1fm/s | 材质[%s]%n",effectType, position, velocity, texture);}
}// 粒子外在状态载体
class Particle {private double x, y;private double velocity;private final ParticleType type; // 共享引用public Particle(double x, double y, double v, ParticleType type) {this.x = x;this.y = y;this.velocity = v;this.type = type;}public void display() {type.render(String.format("(%.1f, %.1f)", x, y), velocity);}
}// 享元工厂
class ParticleFactory {private static final Map<String, ParticleType> pool = new HashMap<>();public static ParticleType getType(String color, String texture, String effect) {String key = color + "_" + texture + "_" + effect;// 池化检测逻辑if (!pool.containsKey(key)) {pool.put(key, new ParticleType(color, texture, effect));}return pool.get(key);}
}// 粒子系统管理
class ParticleSystem {private List<Particle> particles = new ArrayList<>();public void addParticle(double x, double y, double v,String color, String texture, String effect) {ParticleType type = ParticleFactory.getType(color, texture, effect);particles.add(new Particle(x, y, v, type));}public void simulate() {particles.forEach(Particle::display);}
}// 运行示例
class GameEngine {public static void main(String[] args) {ParticleSystem system = new ParticleSystem();// 添加火焰粒子system.addParticle(10.5, 20.3, 5.2, "橙红", "fire_tex", "火焰");system.addParticle(15.1, 18.7, 4.8, "橙红", "fire_tex", "火焰");// 添加冰雪粒子system.addParticle(30.0, 50.0, 2.1, "冰蓝", "snow_tex", "冰霜");system.simulate();}
}

总结

在这里插入图片描述

  1. 享元模式只是一种优化。在应用这个模式之前,你要确定程序里存在内存消耗问题,并且这个问题是跟大量类似对象同时占用内存相关的,同时确保这个问题没办法用其他更好的方式来解决。
  2. 享元(Fly­weight)类包含原始对象里部分能在多个对象中共享的状态。同一享元对象可以在许多不同情景中使用。享元中存储的状态被称为“内在状态”。
  3. 情景(Con­text)类包含原始对象里各不相同的外在状态情景和享元对象组合在一起就能表示原始对象的全部状态
  4. 通常情况下,原始对象的行为会保留在享元类中。因此调用享元的方法必须提供部分外在状态作为参数。但你也可把行为移动到情景类里,然后把连入的享元作为单纯的数据对象。
  5. 客户端(Client)负责计算或存储享元的外在状态。在客户端看来,享元是一种可以在运行时进行配置的模板对象,具体的配置方式就是向他的方法里面传入一些情景数据参数。
  6. 享元工厂(Fly­weight Fac­to­ry)会对已有享元的缓存池进行管理。有了工厂后,客户端就无需直接创建享元,它们只需调用工厂并且向他传递目标享元的一些内在状态就可以了。工厂会根据参数在之前已创建的享元中进行查找,如果找到满足条件的享元就直接返回;如果没有找到就根据参数新建享元。

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

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

相关文章

MFC BCGControlBar

BCGControlBar&#xff08;也称为 BCGSoft 或 BCGControlBar Library&#xff09;是一个用于 MFC&#xff08;Microsoft Foundation Classes&#xff09; 的扩展库&#xff0c;主要提供现代化的 UI 控件、Ribbon 界面、工具栏、属性网格等组件&#xff0c;帮助开发者快速构建专…

【算法手记9】OR26 最长回文子串 NC369 [NOIP2002 普及组] 过河卒

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:刷题 ⚙️操作环境:牛客网 一.OR26 最长回文子串 牛客网题目链接(点击即可跳转):OR26 最长回文子串 题目详情: 本题详情如下图: 题目思路: 本题解题思路如下: 本题思路用中心扩展算法,遍历所有字符,将每个字符作为回文串…

批量删除或替换文本文件中指定的行,如删除第一行、删除最后一行

每一个文本文件中我们都可以插入非常多的行&#xff0c;我们可以对行的内容进行删除、修改等各种操作。如果文本文件中的某些行的内容需要更新&#xff0c;那我们就需要对其进行修改操作。想要修改文本文件的内容其实是非常方便的&#xff0c;但是如果想要批量的对多个文本文件…

LLM架构解析:词嵌入模型 Word Embeddings(第二部分)—— 从基础原理到实践应用的深度探索

本专栏深入探究从循环神经网络&#xff08;RNN&#xff09;到Transformer等自然语言处理&#xff08;NLP&#xff09;模型的架构&#xff0c;以及基于这些模型构建的应用程序。 本系列文章内容&#xff1a; NLP自然语言处理基础词嵌入&#xff08;Word Embeddings&#xff09…

机构数据服务

一、背景说明 券商/基金/银行等金融机构的数据中心&#xff0c;基本都外购有数十家各类数据&#xff0c;自有业务每天也在产生海量信息。如何有效管理和使用这些数据&#xff0c;通过数据服务&#xff0c;沉淀数据资产&#xff0c;机构研发和运维部门也在不断尝试和改进。 传…

中和农信:让金融“活水”精准浇灌乡村沃土

2025年政府工作报告首提“投资于人”概念&#xff0c;并22次提及“金融”&#xff0c;强调要着力抓好“三农”工作&#xff0c;深入推进乡村全面振兴&#xff1b;一体推进地方中小金融机构风险处置和转型发展&#xff1b;扎扎实实落实促进民营经济发展的政策措施&#xff0c;切…

JavaScript重难点突破:期约与异步函数

同步和异步 ​同步&#xff08;Synchronous&#xff09;​ ​定义&#xff1a;任务按顺序依次执行&#xff0c;前一个任务完成前&#xff0c;后续任务必须等待。 ​特点&#xff1a;阻塞性执行&#xff0c;程序逻辑直观&#xff0c;但效率较低 ​异步&#xff08;Asynchron…

学习总结 网格划分+瞬态求解设置

网格划分部分 1.导入几何文件 导入我们的几何模型&#xff0c;他的格式为.scdocx 2.添加局部尺寸BOI 因为要对对前缘和尾缘进行局部加密&#xff0c;所以进行一个BOI的局部加密&#xff0c;目标尺寸取的几何尺寸的最小尺寸的0.1&#xff0c;就是0.4mm。 3.生成表面网格 表面…

.NET 使用 WMQ 连接Queue 发送 message 实例

1. 首先得下载客户端&#xff0c;没有客户端无法发送message. 安装好之后长这样 我装的是7.5 安装目录如下 tools/dotnet 目录中有演示的demo 2. .Net 连接MQ必须引用bin目录中的 amqmdnet.dll 因为他是创建Queuemanager 的核心库&#xff0c; 项目中引用using IBM.WMQ; 才…

风电行业预测性维护解决方案:给风机装上 “智能医生”,实现故障 “秒级预警”

引言&#xff1a;风电设备故障为何成为 “运维黑洞”&#xff1f; 某海上风电场因齿轮箱轴承故障停机 3 天&#xff0c;直接损失 50 万元发电量。传统维护模式下&#xff0c;人工巡检覆盖率不足 40%&#xff0c;故障修复平均耗时 72 小时。而预测性维护通过物联网 AI 技术&am…

5、无线通信基站的FPGA实现架构

基站&#xff08;Base Station&#xff0c;BS&#xff09;&#xff0c;也称为公用移动通信基站&#xff0c;是无线电台站的一种形式&#xff0c;具体则指在一定的无线电覆盖区中&#xff0c;通过移动通信交换中心&#xff0c;与移动电话终端之间的信息传递的无线电收发信电台。…

笔记2——网络参考模型

一、OSI参考模型&#xff1a; 应用层&#xff1a; 报文 给应用程序提供接口 表示层&#xff1a; 进行数据格式的转换 会话层&#xff1a; 在通讯双方之间建立、管理和终止会话 传输层&#xff1a; 数据段&#xff1b;建立、维护、取消一次端到端的数据传输过程&#xff1b;控制…

最短路径:Bellman-Ford算法

Bellman-Ford的操作步骤 1.初始化距离&#xff1a;将起点的dist值设置为0&#xff0c;其他点的dist值设置为无穷大。 2.执行n-1轮松弛操作&#xff1a;遍历所有边&#xff0c;更新最短距离&#xff0c;收敛后可获得最短路径。 3.检测负权环&#xff1a;额外遍历一次&#xf…

0402-对象和类(访问器 更改器 日期类)

OOP&#xff1a;面向对象程序设计 类&#xff1a;构造对象的模板或蓝图 类构造对象的过程称为创建类的实例 封装&#xff1a;对外隐藏数据的真实实现方式&#xff0c;提供简单的方法 &#xff08;类比方向盘&#xff09; 对象&#xff1a;本质上是内存中的一小块空间 识别类&a…

【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的文件上传与下载:实现文件管理功能

<前文回顾> 点击此处查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、开篇整…

搜索算法------DFS练习2

1. 题目 2. 思路和题解 从题目中可以看出&#xff0c;如果一个格子上有雨水&#xff0c;那么就可以流到周围比他高度低的单元格&#xff0c;如果单元格和海洋相邻&#xff0c;那么雨水也会流入海洋。总而言之一句话就是水从高处流向低处。从这里的流向可以联想到深度优先搜索这…

[python] 正则表达式

1.分割str s"1-2--3---4" are.findall(r\d|[-],s) # 输出&#xff1a;[1, -, 2, --, 3, ---, 4]s"-4(2(3)" # ? 表示 - 可以出现0次或1次 # \d 表示匹配一个或多个连续数字 # \D 表示匹配非数字字符 sre.findall(r-?\d|\D,s) # 输出&#xff1a;[-4, (,…

定制化管理系统与通用管理系统,谁更胜一筹?

一、定制化管理系统与通用管理系统的定义与特点 定制化管理系统 定制化管理系统是根据企业的具体业务需求和流程进行个性化开发的软件系统。它能够深度贴合企业的管理需求&#xff0c;提供高度灵活的解决方案。其特点包括&#xff1a; 高度适应性&#xff1a;能够精准匹配企业…

gitee 配置git上传

Git入门&#xff1f;查看 帮助 , Visual Studio / TortoiseGit / Eclipse / Xcode 下如何连接本站, 如何导入仓库 简易的命令行入门教程: Git 全局设置: 以 176fuguM2项目为例 git config --global user.name "堕落圣甲虫" git config --global user.email "11…

SpringBoot+Vue 中 WebSocket 的使用

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议&#xff0c;它使得客户端和服务器之间可以进行实时数据传输&#xff0c;打破了传统 HTTP 协议请求 - 响应模式的限制。 下面我会展示在 SpringBoot Vue 中&#xff0c;使用WebSocket进行前后端通信。 后端 1、引入 j…