设计模式14-享元模式
设计模式14-享元模式
- 由来动机
- 定义与结构
- 代码推导
- 特点
- 享元模式的应用
- 总结
- 优点
- 缺点
- 使用享元模式的注意事项
由来动机
在很多应用中,可能会创建大量相似对象,例如在文字处理器中每个字符对象。在这些场景下,如果每个对象都独立存在,将会导致巨大的内存开销。那么如何在避免大量颗粒度对象问题的同时,让外部客户程序仍然能够透明的使用面向对象的方式来进行操作呢?享元模式的主要动机是通过共享相似对象来减少内存使用,提高系统性能,来解决此类问题。
享元模式适用于以下情况:
- 应用程序使用大量相同或相似对象。
- 对象的大部分状态可以外部化,从而使得许多对象能够共享同一个实例。
- 需要支持大量细粒度对象高效地共享和复用。
定义与结构
模式定义: 运用共享技术有效地支持大量细粒度的对象。–《设计模式》GoF
享元模式由以下几个主要角色组成:
- Flyweight(享元接口): 定义了对象的接口,可以接受外部状态。
- ConcreteFlyweight(具体享元): 实现Flyweight接口,并为内部状态(不变部分)进行存储。
- UnsharedConcreteFlyweight(非共享享元): 并不是所有的Flyweight子类都需要被共享,非共享Flyweight类可以实现Flyweight接口,但它们不是共享的。
- FlyweightFactory(享元工厂): 创建并管理Flyweight对象,确保合理地共享Flyweight。
以下是享元模式的UML图:
代码推导
下面我们通过C++代码示例来实现享元模式。
- 定义Flyweight接口:
class Flyweight {
public:virtual void operation(int extrinsicState) = 0;virtual ~Flyweight() = default;
};
- 实现具体享元类:
#include <iostream>class ConcreteFlyweight : public Flyweight {
private:int intrinsicState;public:ConcreteFlyweight(int state) : intrinsicState(state) {}void operation(int extrinsicState) override {std::cout << "Intrinsic State: " << intrinsicState << ", Extrinsic State: " << extrinsicState << std::endl;}
};
- 实现享元工厂类:
#include <unordered_map>
#include <memory>class FlyweightFactory {
private:std::unordered_map<int, std::shared_ptr<Flyweight>> flyweights;public:std::shared_ptr<Flyweight> getFlyweight(int key) {if (flyweights.find(key) == flyweights.end()) {flyweights[key] = std::make_shared<ConcreteFlyweight>(key);}return flyweights[key];}
};
- 使用享元模式:
int main() {FlyweightFactory factory;std::shared_ptr<Flyweight> fw1 = factory.getFlyweight(1);std::shared_ptr<Flyweight> fw2 = factory.getFlyweight(2);std::shared_ptr<Flyweight> fw3 = factory.getFlyweight(1); // Reuses the existing Flyweightfw1->operation(10);fw2->operation(20);fw3->operation(30);return 0;
}
特点
- 共享对象: 通过共享细粒度对象,减少内存使用,提高性能。
- 内部状态和外部状态: 将对象的状态分为内部状态和外部状态,内部状态存储在享元对象中,而外部状态由客户端传入。
- 不可变性: 享元对象是不可变的,确保共享时不会被其他对象修改。
- 工厂管理: 使用享元工厂管理享元对象的创建和共享,确保唯一实例。
享元模式的应用
享元模式在以下场景中非常有用:
- 文本处理: 文本编辑器中,每个字符都是一个对象,通过享元模式共享字符对象,减少内存消耗。
- 图形系统: 在图形系统中,大量相似的图形对象可以通过享元模式共享,例如树木、建筑物等。
- 缓存: 使用享元模式实现对象缓存,避免重复创建相同的对象。
- 游戏开发: 在游戏开发中,使用享元模式共享相同类型的游戏对象,例如敌人、子弹等,减少内存使用。
总结
- 面对对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体我们需要考虑对象的代价问题。享元模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
- 享元模式采用对象共享的方法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实践方面要注意对象状态的处理。
- 对象的数量太大,从而导致对象内存开销加大,什么样的数量才算大?这需要我们仔细的根据具体应用情况进行评估,而不能评空臆断。
优点
- 减少内存消耗: 通过共享细粒度对象,享元模式大大减少了内存的使用。当有大量相似对象需要创建时,享元模式特别有效。
- 提升性能: 由于减少了内存使用,享元模式可以提升系统的性能,特别是在内存有限或系统资源紧张的情况下。
- 分离内部状态和外部状态: 享元模式将对象的状态分为内部状态(共享部分)和外部状态(非共享部分),使得对象更加轻量化。
缺点
- 复杂性增加: 享元模式引入了额外的复杂性,特别是在处理对象状态时,需要明确区分内部状态和外部状态。
- 维护困难: 享元模式需要维护一个共享对象的池,这增加了代码的复杂性和维护难度。
- 线程安全问题: 在多线程环境下,需要确保享元对象的线程安全性,可能需要引入额外的同步机制,影响性能。
使用享元模式的注意事项
- 区分内部状态和外部状态: 在使用享元模式时,需要明确区分哪些状态可以共享(内部状态),哪些状态是对象独有的(外部状态)。
- 适用场景: 享元模式适用于有大量相似对象的场景,例如图形系统、文本处理、游戏开发等。如果对象数量不多,或者对象之间差异较大,使用享元模式的意义不大。
- 线程安全: 在多线程环境下,共享的享元对象需要是线程安全的。可以使用锁(例如
std::mutex
)或者其他同步机制确保线程安全。 - 内存泄漏: 享元模式中的享元工厂负责管理共享对象,需要确保及时释放不再使用的共享对象,以避免内存泄漏。
- 性能权衡: 虽然享元模式可以减少内存使用,但也会引入一些性能开销,例如对象池的维护、同步机制的使用等。在设计时需要权衡这些开销。
相关文章:

设计模式14-享元模式
设计模式14-享元模式 由来动机定义与结构代码推导特点享元模式的应用总结优点缺点使用享元模式的注意事项 由来动机 在很多应用中,可能会创建大量相似对象,例如在文字处理器中每个字符对象。在这些场景下,如果每个对象都独立存在,…...
Javascript中canvas与svg详解
Canvas 在JavaScript中,<canvas> 元素用于在网页上绘制图形,如线条、圆形、矩形、图像等。它是一个通过JavaScript和HTML的<canvas>元素来工作的绘图表面。<canvas> 元素自身并不具备绘图能力,它仅仅提供了一个绘图环境&a…...

【BUG】已解决:No Python at ‘C:Users…Python Python39python. exe’
No Python at ‘C:Users…Python Python39python. exe’ 目录 No Python at ‘C:Users…Python Python39python. exe’ 【常见模块错误】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班…...

Flink SQL 的工作机制
前言 Flink SQL 引擎的工作流总结如图所示。 从图中可以看出,一段查询 SQL / 使用TableAPI 编写的程序(以下简称 TableAPI 代码)从输入到编译为可执行的 JobGraph 主要经历如下几个阶段: 将 SQL文本 / TableAPI 代码转化为逻辑执…...
[AI Mem0] 源码解读,带你了解 Mem0 的实现
Mem0 的 CRUD 到底是如何实现的?我们来看下源码。 使用 先来看下,如何使用 Mem0 import os os.environ["OPENAI_API_KEY"] "sk-xxx"from mem0 import Memorym Memory()# 1. Add: Store a memory from any unstructured text re…...

【LLM】-10-部署llama-3-chinese-8b-instruct-v3 大模型
目录 1、模型下载 2、下载项目代码 3、启动模型 4、模型调用 4.1、completion接口 4.2、聊天(chat completion) 4.3、多轮对话 4.4、文本嵌入向量 5、Java代码实现调用 由于在【LLM】-09-搭建问答系统-对输入Prompt检查-CSDN博客 关于提示词注入…...

C语言 之 理解指针(4)
文章目录 1. 字符指针变量2. 数组指针变量2.1 对数组指针变量的理解2.2 数组指针变量的初始化 3. 二维数组传参的本质4. 函数指针变量4.1 函数指针变量的创建4.2 函数指针变量的使用 5. 函数指针数组 1. 字符指针变量 我们在前面使用的主要是整形指针变量,现在要学…...

Java设计模式—单例模式(Singleton Pattern)
目录 一、定义 二、应用场景 三、具体实现 示例一 示例二 四、懒汉与饿汉 饿汉模式 懒汉模式 五、总结 六、说明 一、定义 二、应用场景 单例模式的应用场景主要包括以下几个方面: 日志系统:在应用程序中,通常只需要一个日…...

AV1帧间预测(二):运动补偿
运动补偿(Motion Compensation,MC)是帧间预测最基础的工具,AV1支持两种运动补偿方式,一种是传统的平移运动补偿,另一种是仿射运动补偿。下面分别介绍这两种运动补偿方法。 平移运动补偿 平移运动补偿是最传统的运动补偿方式,H.26…...
数学建模(5)——逻辑回归
一、二分类 import numpy as np import matplotlib.pyplot as plt from sklearn import datasets from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklea…...

【C++高阶】:深入探索C++11
✨ 心似白云常自在,意如流水任东西 🌏 📃个人主页:island1314 🔥个人专栏:C学习 🚀 欢迎关注:👍点赞 Ǵ…...
6. 自定义Docker镜像
如何自定义Docker镜像:从基础到实践 Docker作为一个容器化平台,使得应用的打包、分发和运行变得更加高效和便捷。本文将详细介绍如何自定义一个Docker镜像,包括镜像的构成、分层原理、创建自定义镜像的具体步骤,并演示如何打包和…...

「12月·长沙」人工智能与网络安全国际学术会议(ISAICS 2024)
人工智能与网络安全国际学术会议(ISAICS 2024)将于2024年12月20日-2024年12月22日在湖南长沙召开。会议中发表的文章将会被收录,并于见刊后提交EI核心索引。会议旨在在为国内与国际学者搭建交流平台,推进不同学科领域的融合发展,就当今人工智能与网络安全范畴内各学…...

【技术支持案例】使用S32K144+NSD8381驱动电子膨胀阀
文章目录 1. 前言2. 问题描述3. 理论分析3.1 NSD8381如何连接电机3.2 S32K144和NSD8381的软件配置 4.测试验证4.1 测试环境4.2 测试效果4.3 测试记录 1. 前言 最近有客户在使用S32K144NSD8381驱动电子膨胀阀时,遇到无法正常驱动电子膨胀阀的情况。因为笔者也是刚开…...

第二期:集成电路(IC)——智能世界的微观建筑大师
嘿,小伙伴们!👋 我是你们的老朋友小竹笋,一名热爱创作和技术的工程师。上一期我们聊了聊AI芯片,这次我们要深入到更微观的层面,来探究集成电路(IC)的世界。准备好一起探索了吗&#…...

基于物联网的区块链算力网络,IGP/BGP协议
目录 基于物联网的区块链算力网络 IGP/BGP协议 IGP(内部网关协议) BGP(边界网关协议) 内部使用ISP的外部使用BGP的原因 一、网络规模和复杂性 二、路由协议的特性 三、满足业务需求 四、结论 基于物联网的区块链算力网络 通 过 多个物联网传感器将本地计算…...
每日一题~960 div2 A+B+C(简单奇偶博弈,构造,观察性质算贡献)
A题意: N 长的数组。 一次操作: 最开始的mx 为零。 选出一个数(使得这个数>mx) ,之后将mx 更新为这个数,将这个数置为零。 不能做这个操作的,输。 问是否有先手赢的策略。有的话,输出yes 否则no 当时一…...

音视频入门基础:H.264专题(17)——FFmpeg源码获取H.264裸流文件信息(视频压缩编码格式、色彩格式、视频分辨率、帧率)的总流程
音视频入门基础:H.264专题系列文章: 音视频入门基础:H.264专题(1)——H.264官方文档下载 音视频入门基础:H.264专题(2)——使用FFmpeg命令生成H.264裸流文件 音视频入门基础&…...

Aboboo一些操作
常用快捷键⌨ 快捷键/操作方式 功能 鼠标中键/Esc 进入/退出全屏 空格/Tab 暂停/恢复播放 左/右箭头 快退/快进 Ctrl-左/右箭头 30秒快退/快进 Alt-左/右箭头 60秒快退/快进 Ctrl-Alt-左/右箭头 播放速率调节 PageUp/PageDown 上一句/下一句 上下箭头/滚轮 …...
获取行号LineNumberReader
(每日持续更新)jdk api之LineNumberReader基础、应用、实战-CSDN博客...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...

向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...

轻量级Docker管理工具Docker Switchboard
简介 什么是 Docker Switchboard ? Docker Switchboard 是一个轻量级的 Web 应用程序,用于管理 Docker 容器。它提供了一个干净、用户友好的界面来启动、停止和监控主机上运行的容器,使其成为本地开发、家庭实验室或小型服务器设置的理想选择…...

动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...