C++ 设计模式——享元模式

C++ 设计模式——享元模式

    • C++ 设计模式——享元模式
      • 1. 主要组成成分
      • 2. 享元模式内部状态
      • 3. 享元模式外部状态
      • 4. 逐步构建享元模式
        • 4.1 抽象享元类定义
        • 4.2 具体享元类实现
        • 4.3 享元工厂类实现
        • 4.4 主函数
      • 5. 享元模式 UML 图
        • 享元模式 UML 图解析
      • 6. 享元模式的优点
      • 7. 享元模式的缺点
      • 8. 适用场景
      • 完整代码

C++ 设计模式——享元模式

享元模式(Flyweight Pattern)是一种结构型设计模式,用于通过共享相似对象来减少内存使用和提高性能。该模式特别适用于需要大量相似对象的场景,通过将对象的状态分为内部状态和外部状态,来实现高效的对象共享。

享元模式也称为蝇量模式,旨在解决面向对象程序设计中的性能问题。享元的英文名“Flyweight”意为“轻量级”,源于拳击比赛中较轻的选手。该模式的核心目标是让对象变得“轻”,即减少内存占用。在需要某个对象时,尽量共享已经创建的同类对象,以避免频繁使用 new 创建同类或相似的对象。在对象数量非常庞大的情况下,这种共享可以显著节省内存占用并提升程序运行效率。

1. 主要组成成分

  • 抽象享元角色(Flyweight):享元对象的抽象基类或接口,定义了对象的外部状态和内部状态的接口或实现。
  • 具体享元角色(Concrete Flyweight):实现抽象享元类中的方法,是需要共享的对象类。
  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保享元对象的共享。

2. 享元模式内部状态

  • 定义:内部状态是对象在共享时可以保持不变的状态。这些状态是可以被多个对象共享的,因此在享元模式中,内部状态通常被存储在享元对象中。
  • 特点:内部状态不依赖于具体的环境或上下文,可以在多个享元对象之间共享。例如,在棋盘游戏中,棋子的颜色(黑色或白色)就是内部状态。

3. 享元模式外部状态

  • 定义:外部状态是与具体上下文相关的状态,通常在对象使用时作为参数传递。外部状态在不同的场景下可能会变化,因此不能被共享。
  • 特点:外部状态依赖于具体的环境或上下文,通常在调用享元对象的方法时传递。例如,在棋盘游戏中,棋子的位置(如坐标)就是外部状态。

4. 逐步构建享元模式

以下是一个简单的享元模式示例,模拟一个棋盘游戏中的棋子绘制系统,其中可能会有大量相似的棋子对象。

4.1 抽象享元类定义

定义棋子的基本结构。EnumColor 枚举表示棋子的颜色,Position 结构体表示棋子的位置。Piece 是一个抽象类,定义了绘制棋子的接口。

enum EnumColor  //棋子颜色
{Black,  //黑White   //白
};struct Position //棋子位置
{int m_x;int m_y;Position(int tmpx, int tmpy) :m_x(tmpx), m_y(tmpy) {} //构造函数
};class Piece //棋子抽象类
{
public:virtual ~Piece() {} //做父类时析构函数应该为虚函数public:virtual void draw(Position tmppos) = 0;
};
4.2 具体享元类实现

实现具体的棋子类,分别为黑色和白色棋子。每个类实现了 draw 方法,负责在指定位置绘制棋子。通过共享这些具体棋子类,减少内存使用。

class BlackPiece : public Piece //黑色棋子
{
public:virtual void draw(Position tmppos){cout << "在位置:(" << tmppos.m_x << "," << tmppos.m_y << ")处绘制了一个黑色棋子!" << endl;}
};
class WhitePiece : public Piece //白色棋子
{
public:virtual void draw(Position tmppos){cout << "在位置:(" << tmppos.m_x << "," << tmppos.m_y << ")处绘制了一个白色棋子!" << endl;}
};
4.3 享元工厂类实现

工厂类负责创建和管理棋子对象。它使用 std::map 存储已经创建的棋子对象,以便在需要时返回共享的对象。析构函数确保释放内存,防止内存泄漏。

class pieceFactory  //创建棋子的工厂
{
public:~pieceFactory() //析构函数{//释放内存for (auto iter = m_FlyWeihgtMap.begin(); iter != m_FlyWeihgtMap.end(); ++iter){Piece* tmpfw = iter->second;delete tmpfw;}m_FlyWeihgtMap.clear();//这句其实可有可无}
public:Piece* getFlyWeight(EnumColor tmpcolor) //获取享元对象,也就是获取被共享的棋子对象{auto iter = m_FlyWeihgtMap.find(tmpcolor);if (iter == m_FlyWeihgtMap.end()){//没有该享元对象,那么就创建出来Piece* tmpfw = nullptr;if (tmpcolor == Black) //黑子{tmpfw = new BlackPiece();}else //白子{tmpfw = new WhitePiece();}m_FlyWeihgtMap.insert(make_pair(tmpcolor, tmpfw));//以棋子颜色枚举值为key,增加条目到map中return tmpfw;}else{return iter->second;}}
private://在文件头增加#include <map>std::map<EnumColor, Piece*> m_FlyWeihgtMap; //用map容器来保存所有的享元对象,一共就两个享元对象(黑色棋子一个,白色棋子一个)
};
4.4 主函数
int main()
{pieceFactory* pfactory = new pieceFactory();Piece* p_piece1 = pfactory->getFlyWeight(Black);p_piece1->draw(Position(3, 3));//黑子落子到3,3位置Piece* p_piece2 = pfactory->getFlyWeight(White);p_piece2->draw(Position(5, 5));//白子落子到5,5位置Piece* p_piece3 = pfactory->getFlyWeight(Black);p_piece3->draw(Position(4, 6));//黑子落子到4,6位置Piece* p_piece4 = pfactory->getFlyWeight(White);p_piece4->draw(Position(5, 7));//白子落子到5,7位置//释放资源delete pfactory;return 0;
}

执行结果:

在位置:(3,3)处绘制了一个黑色棋子!
在位置:(5,5)处绘制了一个白色棋子!
在位置:(4,6)处绘制了一个黑色棋子!
在位置:(5,7)处绘制了一个白色棋子!

5. 享元模式 UML 图

享元模式 UML 图

享元模式 UML 图解析
  • Flyweight (抽象享元类):通常是一个接口或抽象类。在该类中声明各种享元类的方法,外部状态可以作为参数传递到这些方法中。这里的抽象享元类是 Piece,方法是 draw,外部状态(棋子的位置)通过 draw 方法的参数传递。
  • Concrete Flyweight (具体享元类):抽象享元类的子类,用这些类创建的对象就是享元对象。这里指 BlackPieceWhitePiece 类。
  • Flyweight Factory (享元工厂类):用于创建并管理享元对象,存在一个享元池(一般使用 std::map 存储键值对)。当用户请求一个享元对象时,该工厂返回一个已创建的享元对象,或者如果请求的对象不存在,则新创建一个并放入享元池。

6. 享元模式的优点

  • 节省内存:通过共享相似对象,减少了内存使用,尤其在需要大量相似对象的情况下。
  • 提高性能:减少了对象的创建和销毁次数,提高了系统性能。
  • 清晰的结构:通过将共享和非共享的状态分开,代码结构更加清晰。

7. 享元模式的缺点

  • 复杂性增加:引入享元模式会增加代码的复杂性,尤其是在管理共享对象时。
  • 外部状态管理:外部状态需要单独管理,可能导致代码的可读性降低。
  • 类数量增加:每个不同的共享状态都需要一个具体的享元类,可能导致类的数量增加。

8. 适用场景

  • 大量相似对象:当系统需要创建大量相似对象时,享元模式可以有效减少内存开销。
  • 对象状态分离:对象的状态可以分为内部状态和外部状态,适合使用享元模式来管理。
  • 性能优化:在性能敏感的应用中,使用享元模式可以显著提高效率。

完整代码

#include <iostream>
#include <list>
#include <map>using namespace std;enum EnumColor  //棋子颜色
{Black,  //黑White   //白
};struct Position //棋子位置
{int m_x;int m_y;Position(int tmpx, int tmpy) :m_x(tmpx), m_y(tmpy) {} //构造函数
};class Piece //棋子抽象类
{
public:virtual ~Piece() {} //做父类时析构函数应该为虚函数public:virtual void draw(Position tmppos) = 0;
};class BlackPiece : public Piece //黑色棋子
{
public:virtual void draw(Position tmppos){cout << "在位置:(" << tmppos.m_x << "," << tmppos.m_y << ")处绘制了一个黑色棋子!" << endl;}
};
class WhitePiece : public Piece //白色棋子
{
public:virtual void draw(Position tmppos){cout << "在位置:(" << tmppos.m_x << "," << tmppos.m_y << ")处绘制了一个白色棋子!" << endl;}
};class pieceFactory  //创建棋子的工厂
{
public:~pieceFactory() //析构函数{//释放内存for (auto iter = m_FlyWeihgtMap.begin(); iter != m_FlyWeihgtMap.end(); ++iter){Piece* tmpfw = iter->second;delete tmpfw;}m_FlyWeihgtMap.clear();//这句其实可有可无}
public:Piece* getFlyWeight(EnumColor tmpcolor) //获取享元对象,也就是获取被共享的棋子对象{auto iter = m_FlyWeihgtMap.find(tmpcolor);if (iter == m_FlyWeihgtMap.end()){//没有该享元对象,那么就创建出来Piece* tmpfw = nullptr;if (tmpcolor == Black) //黑子{tmpfw = new BlackPiece();}else //白子{tmpfw = new WhitePiece();}m_FlyWeihgtMap.insert(make_pair(tmpcolor, tmpfw));//以棋子颜色枚举值为key,增加条目到map中return tmpfw;}else{return iter->second;}}
private://在文件头增加#include <map>std::map<EnumColor, Piece*> m_FlyWeihgtMap; //用map容器来保存所有的享元对象,一共就两个享元对象(黑色棋子一个,白色棋子一个)
};int main()
{pieceFactory* pfactory = new pieceFactory();Piece* p_piece1 = pfactory->getFlyWeight(Black);p_piece1->draw(Position(3, 3));//黑子落子到3,3位置Piece* p_piece2 = pfactory->getFlyWeight(White);p_piece2->draw(Position(5, 5));//白子落子到5,5位置Piece* p_piece3 = pfactory->getFlyWeight(Black);p_piece3->draw(Position(4, 6));//黑子落子到4,6位置Piece* p_piece4 = pfactory->getFlyWeight(White);p_piece4->draw(Position(5, 7));//白子落子到5,7位置//释放资源delete pfactory;return 0;
}

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

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

相关文章

Linux驱动学习之中断与等待队列

本篇分为设备树部分和API接口部分 设备树 想要使用中断&#xff0c;设备树中需要有两个属性&#xff1a; interrupts // 表示要使用哪一个中断, 中断的触发类型等等。 interrupt-parent // 这个中断要接到哪一个设备去? 即父中断控制器是谁 父中…

趣味算法------拯救阿拉德大陆

目录 ​编辑 题目描述&#xff1a; 思路解析&#xff1a; 具体代码&#xff1a; 总结&#xff1a; 题目描述&#xff1a; 此时一批勇士也随之而来&#xff0c;但其能力也是参差不齐&#xff0c;我们需要挑选出最优秀的勇士来守护这片大陆。每位勇士都有属于自己的编号&am…

JobSchedulerService.setRequiresCharging需充电且电量大于90%才触发的现象

一、摘要 从源码看原生JobSchedulerService.setRequiresCharging 的特性&#xff0c;该特性竞品机器华为、Oppo也是如此。 1、应用处于前台可见&#xff0c;满足充电条件&#xff0c;立刻触发 2、应用处于后台不可见&#xff0c;需要设备连接USB或AC且电量大于90%&#xff0…

挂个人-CSDN Java优秀内容博主rundreamsFly抄袭

事件起因 今天点开自己的CSDN博客&#xff0c;发现给我推了一篇文章抄袭我自己昨天18点发的文章。 就是这篇&#xff0c;一字不差&#xff0c;博主昵称是&#xff1a;rundreamsFly&#xff0c;账号是rundreams。 抄袭者文章 发布于2024-8-26 19:37:41秒&#xff0c;比我发布…

C的温故而知新:位操作(C Primer Plus第十五章)

第十五章&#xff1a;位操作 这一章的篇幅不是很长&#xff0c;但既然能单独作为一章来讲的话&#xff0c;应该蛮重要的&#xff0c;但是我貌似没有总结出多少需要注意、加强记忆的东西&#xff0c;可见在JAVA的日常开发过程中基本不太遇见有关位操作的内容&#xff0c;所以我…

FSQ26信号分析仪RS FSU26 20HZ-26.5G频谱分析仪

罗德与施瓦茨Rohde & Schwarz FSQ26信号分析仪&#xff0c;20 Hz - 26.5 GHz ​R&S FSQ26 信号分析仪集两种仪器于一身。它提供高达 120 MHz 解调带宽的信号分析&#xff0c;并具有高端频谱分析仪的动态范围。 频率范围&#xff1a;20 Hz 至 26.5 GHz 高端频谱分析仪…

神经网络—卷积层

1.讲解 Conv2d out_channels 参数为2时&#xff0c;会生成两个卷积核&#xff0c;分别与输入进行卷积。得到的两个输出为输出 新生成的卷积核和原来的卷积核不一定相同 in_channels (int) – Number of channels in the input image out_channels (int) – Number of channels…

ARM32开发——(六)GPIO_USART通信原理

1. 串行通信和并行通信 1.1 串行通信 串行通信是一种数据传输的方式&#xff0c;它是指将数据按照一位一位的顺序依次发送和接收&#xff0c;常用于远距离通信、嵌入式系统和低带宽传输场景下。串行通信相对于并行通信而言&#xff0c;只需要传输一条数据线&#xff0c;相对简…

一文了解机器学习顶会ICML 2024的研究热点

对人工智能研究领域前沿方向的跟踪是提高科研能力和制定科研战略的关键。本文通过图文并茂的方式介绍了ICML 2024的研究热点&#xff0c;帮助读者了解和跟踪机器学习和人工智能的前沿研究方向。本推文的作者是许东舟&#xff0c;审校为邱雪和黄星宇。 1 会议介绍 ICML&#x…

运放阻抗和噪声(同相放大器的输入/输出阻抗 + 电压跟随器阻抗 + 噪声 +信噪比)

2024-8-27&#xff0c;星期一&#xff0c;21:03&#xff0c;天气&#xff1a;阴雨&#xff0c;心情&#xff1a;晴。培训终于结束啦&#xff0c;开始轮岗了&#xff0c;看了两天PPT&#xff0c;加油加油&#xff0c;继续学习。 今天继续学习第六章运算放大器&#xff0c;主要学…

一文带你从零到实战,学会gcc和Makefile,多文件编译神器的使用与编写

目录&#xff1a; 目录&#xff1a; 一、什么是Makefile 1.1 makefile的作用&#xff1a; 1.2 makefile的基本组成&#xff1a; 二、Linux编译过程&#xff1a; 2.1 linux编译过程: 2.1.1 预处理&#xff08;Preprocessing&#xff09; 2.1.2 编译&#xff08;Compilation&am…

Android Studio 自定义字体大小

常用编程软件自定义字体大全首页 文章目录 前言具体操作1. 打开设置对话框2. 选择外观字体 前言 Android Studio 自定义字体大小&#xff0c;统一设置为 JetBrains Mono &#xff0c;大小为 14 具体操作 【File】>【Settings...】>【Appearance & Behavior】>【…

二、设置地图配置表

一、导入一个背景图 由于背景图比较大&#xff0c;需要缩小至0.73 二、写配置文件&#xff08;SO&#xff09; 使用List需要一个命名空间 写一个类&#xff0c;声明房间的出现数量和种类&#xff1b;将它实例化出来 三、枚举变量的多选 在枚举变量中标记命名空间&#xff…

docker 多线成服务,比如gunicorn服务启动报错解决办法

docker执行的时候报错&#xff0c;排查是线程创建权限不足导致的&#xff0c;报错如下。 解决办法 docker run -e OPENBLAS_NUM_THREADS1 your_image

Unity XR Interaction Toolkit 踩坑记录

1&#xff1a;按下 grap/select 键 物品直接飞到手上 2 按下 grap/select 键 物品一点点的想自己移动

OpenCV杂项图像变换(2)线性混合函数blendLinear()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 执行两个图像的线性混合&#xff1a; dst ( i , j ) weights1 ( i , j ) ∗ src1 ( i , j ) weights2 ( i , j ) ∗ src2 ( i , j ) \texttt{…

关于多线程你了解多少?

或许是执念太重&#xff0c;又或许是性格缺陷&#xff0c;我对java中一些知识的坚持&#xff0c;已经到了让人无法接受的地步。有些人甚至因此在背后骂我神经病、傻瓜。但我依旧我行我素&#xff0c;即使中间懈怠了很长时间&#xff0c;重新开始时我依旧会以这些知识为起点。不…

Ubuntu上搭建Nginx环境

1. 软件包下载 nginx下载地址 下载linux版本的nginx&#xff0c;如图圈示 2. 将下载好的软件包上传至Linux服务器 假设上传到 /opt/nginx 目录,进入目录 cd /opt/nginx解压&#xff0c;根据版本自行修改版本号 tar zxvf nginx-1.16.0.tar.gz3.安装 安装编译所需的依赖&a…

前端算法 === 力扣 111 二叉树的最小深度

目录 问题描述 DFS&#xff08;深度优先搜索&#xff09;方案 BFS&#xff08;广度优先搜索&#xff09;方案 总结 力扣&#xff08;LeetCode&#xff09;上的题目111是关于二叉树的最小深度问题。这个问题可以通过深度优先搜索&#xff08;DFS&#xff09;和广度优先搜索&…

QJson的写入和解析基本操作

一、QJson简介 QJson 是一个用于处理 JSON&#xff08;JavaScript Object Notation&#xff09;数据的 C 库 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式 JSON 的语法简洁明了&#xff0c;使用人类可读的文本格式来表示数据 它由键值…