设计模式——1_5 享元(Flyweight)
今人不见古时月,今月曾经照古人
——李白
文章目录
- 定义
- 图纸
- 一个例子:可以复用的样式表
- 绘制表格
- 降本增效?
- 第一步,先分析 变化和不变的地方
- 第二步,把变化和不变的地方拆开来
- 第三步:有没有办法共享这些内容完全相同的样式对象?
- 碎碎念
- 抽象变化的部分 & 抽象不变的部分
- 享元和单例
- 享元和String.intern()
- 享元和活字印刷
定义
运用共享技术有效地支持大量颗粒度对象
享元 真是一个非常非常优秀的翻译
如果你单看 四人组 对享元的定义,那很容易就会把他理解成类似 连接池 那种对对象进行共享的模式。但是如果享元就是对对象池的管理的话,那他的元字如何解释呢?
窃以为,这里的元应该做 【元件】 讲,共享元件,才叫享元
。享元不是对整个大对象进行对象池管理,而是对大对象中的重复部分抽离出来进行管理。
图纸
一个例子:可以复用的样式表
在 css 中,我们会把经常用到的样式组合成一个 css 类,然后在标签中通过 class=XX 的方式让N个标签引用同样的样式
如果你用过 jxl 或者 poi 这样的框架操作过 Excel 表格的话,就会发现你可以在一个 Sheet 里创建的样式是有上限的。可是 C# 里用于操作 office 的框架却没有这个烦恼。当然微软一家亲肯定有一些优化,可问题在于他怎么做到的呢?
准备好了吗?这次的例子开始了:
绘制表格
假如我们现在要 绘制一个表格的框架,就像这样:
//格
public class Cell {//设定字体private Font font;//背景颜色private Color background;//要展示的值private String value;public void draw(){System.out.println("根据font+background+value进行绘制");}…… getter & setter & other 略
}//行
public class Row {private final List<Cell> cells;public Row(List<Cell> cells) {this.cells = cells;}public void draw(){for (Cell cell : cells) {cell.draw();}}……
}//表
public class Sheet {private final List<Row> rows;public Sheet() {rows = new ArrayList<>();}public void draw() {for (Row row : rows) {row.draw();}}……
}
这个框架特别好,我就喜欢这种一眼能看到头不藏着掖着的框架。这玩意,他单纯
可是太单纯也有问题,某天我们用他处理一个 1000*10 的表格的时候他直接就崩溃了
经过分析我们发现在绘制表格的时候程序占用的内存异常高,10000个格子,意味着我们会有10000个cell对象 和 10000个Font对象 以及 10000个Color对象,有没有办法简化他?
降本增效?
重构代码的方式总是大同小异的
第一步,先分析 变化和不变的地方
我们发现,1000个Row对象和10000个Cell对象 肯定是节约不了的,而 Cell 中最关键的 Value 每一格都不相同,哪怕碰巧一致,不确定性太多,无法公用
那就只能从 样式
上打主意了,Cell 通过 一个 Font 对象 和 一个 Color 对象来管理 Cell 的样式,而一个表格里面字体不超过5种,颜色不超过10种。我们却创建了整整 10000 个 Font 对象和 10000 个 Color 对象
。这显然是不合理的
第二步,把变化和不变的地方拆开来
这一步已经完成了, 因为 Cell 和 Font、Color 本来就是通过组合绑定在一起的
第三步:有没有办法共享这些内容完全相同的样式对象?
当然有
我们可以创建 关于Font的对象池 和 关于Color的对象池,然后创建对应的工厂方法,要求 client 必须通过工厂方法来获取 Font 和 Color 对象。当我们发现 client 获取已经在池中存在的 Font、Color 对象的时候,直接从池里面把 已有对象
返还给 client;如果 client 获取的是全新的样式,则新建对应的 Font、Color 对象返还给 client,并把新建的对象写入池中
就像这样:
public class StyleFactory {//字体池private List<Font> fontPool = new LinkedList<>();//颜色池private List<Color> colorPool = new LinkedList<>();public Font getFont(int size, String fontFamily, Color color) {for (Font font : fontPool) {if (font.getSize() == size&& font.getFontFamily().equals(fontFamily)&& font.getColor().equals(color)/*Color的equals方法已经重构过了*/) {return font;}}Font result = new Font(size, fontFamily, color);fontPool.add(result);return result;}public Color getColor(int red, int green, int blue) {for (Color color : colorPool) {if (color.getRed() == red&& color.getGreen() == green&& color.getBlue() == blue) {return color;}}Color result = new Color(red, green, blue);colorPool.add(result);return result;}
}
我们增加了 fontPool
和 colorPool
这两个列表用于管理程序中已经出现过的 Font、Color对象
然后通过定义 StyleFactory
的方式管理 Font、Color 对象创建的方式,同时管理池,最终实现相同样式的共享,从而大大降低了内存损耗
而这正是一个标准的 享元 实现
为啥要用遍历而不是映射表
因为没必要,上文已经讲过 Font 在一个表格中出现次数不超过 5 种,Color 不超过 10 种。这种数量级的遍历根本不会对性能产生影响
碎碎念
抽象变化的部分 & 抽象不变的部分
在之前的设计模式中,我们通常是以不变的部分作为基层,把变化的部分剥离开来,让他去和不变的部分进行组合以实现降低耦合的效果
但享元比较特殊,他是少有的剥离不变的部分,以变化的部分为基准,让变化的部分去找不变的部分的设计模式
享元和单例
如果把享元的内容从对象内的某些状态,拓展到整个对象,这时候的享元就是对整个对象的对象池管理,此时我们甚至可以说单例是极致的享元
但是享元和单例的本质是不同的
- 单例讲究从根本上只有一个对象,而且这一个对象的状态也是共享的部分,所以他也应该是可变的
- 享元讲究一种状态一个对象,一个享元对象被创建出来后,他的状态应该是不可变的。因为当你改变享元对象的状态时,所有引用这个对象的外部对象都会因此而改变,这并不是我们用享元想要看到的效果。
所以硬要在创建型模式里找一个跟享元像的话,倒不如说是原型的思想更像一点:
- 对原型来说,创建一个新对象花销太大了,那行我不创建了,我复制
- 对享元来说,大量对象都有 地址不同的相同状态 占用太大了,那行别创建了,大家都用同一个就完事了
享元和String.intern()
首先我们要知道这个intern方法是用来干嘛的:
String 中的 intern() 是一个本地方法,他的作用为:假如一个字符串在 常量池 中已经存在了,则返还常量池中的该字符串;如果不存在,则把该字符串放入常量池,再返还常量池中的该字符串对象的引用
有没有觉得他跟享元超级像,其实Java中的String就是用了享元的思想。我们知道String是不可变的,我们创建的字符串会被放到某个公共区域(常量池),以便程序中所有的类共享这些字符串信息。JVM一定要这样管控字符串,如果为所有的字符串都创建全新的信息,那一下子就爆内存了。
可别认为这只是一个可知可不知的冷知识,面试官是很喜欢问问他有关的问题的,比如他可以这样问:
请问以下代码会如何输出:
String str1 = new StringBuilder("a").append("b").toString();
System.out.println(str1.intern() == str1);String str2 = new StringBuilder("ja").append("va").toString();
//String str2 = new StringBuilder("a").append("b").toString(); //这样写效果也是一样的
System.out.println(str2.intern() == str2);
答案是这样的:
- 1.6及以下时输出 false、false
- 1.7及以上时输出 true、false
至于原因,咱这是讲设计模式的,就不展开了(这玩意跟堆 栈 永久代有关系,太长了,有机会整理JVM的笔记时再唠吧)
享元和活字印刷
就是古代四大发明里面哪个活字印刷
仔细想想,其实活字印刷就是享元思想的体现:
把每个字都制作成小方块放到字库中,需要的时候先到字库里面找,如果没找到,刻一个先用,用完再把刚刻好的那个方块放到字库中,方便下次调用
古人的智慧真是不可小觑
万分感谢您看完这篇文章,如果您喜欢这篇文章,欢迎点赞、收藏。还可以通过专栏,查看更多与【设计模式】有关的内容
相关文章:

设计模式——1_5 享元(Flyweight)
今人不见古时月,今月曾经照古人 ——李白 文章目录 定义图纸一个例子:可以复用的样式表绘制表格降本增效?第一步,先分析 变化和不变的地方第二步,把变化和不变的地方拆开来第三步:有没有办法共享这些内容完…...

kafka系列(二)
本章承接kafka一内容,文章在本人博客主页都有,可以自行点击浏览。 幂等性 请求执行多次,但执行的结果是一致的。 如果,某个系统是不具备幂等性的,如果用户重复提交了某个表格,就可能会造成不良影响。例如…...

Ubuntu20.04安装配置OpenCV-Python库并首次执行读图
一、选择三方提供的预编译包安装: 可以从官网下载 OpenCV 的安装包,编译后使用;也可以直接使用第三方提供的预编译包 安装。显然后者不需要执行编译步骤,更便捷。选择由 PyPI 提供的 OpenCV 安装包,可以在 https://py…...

经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练
经典目标检测YOLO系列(二)YOLOV2的复现(2)正样本的匹配、损失函数的实现及模型训练 我们在之前实现YOLOv1的基础上,加入了先验框机制,快速的实现了YOLOv2的网络架构,并且实现了前向推理过程。 经典目标检测YOLO系列(二)YOLOV2的复现(1)总体…...

半波整流电路原理详解+参数与计算公式
什么是半波整流电路? 半波整流电路的基本操作非常简单,输入信号通过二极管,由于只能通过一个方向的电流,二极管的整流作用,单个二极管只允许通过一半的波形。 下图说明了半波整流电路的基本原理。 半波整流电路工作图…...

GZ036 区块链技术应用赛项赛题第3套
2023年全国职业院校技能大赛 高职组 “区块链技术应用” 赛项赛卷(3卷) 任 务 书 参赛队编号: 背景描述 新能源作为新兴领域,产业呈现碎片化与复杂化的特性,逐渐出现管理困难、供应链金融、可信监管与数…...

LeetCode142.环形链表II
力扣题目链接 思路:判断链表是否有环?可以使用快慢指针法,快指针每次走两步,慢指针每次走一步,如果链表有环一定会在环中相遇。 如何找环的入口?当快慢指针在环中第一次相遇时,让快指针从头结…...

触摸按键控制LED灯
目录 1.理论 2.代码 2.1 touch_ctrl_led.v 2.2 tb_touch_ctrl_led 1.理论 以上的波形图的touch_flag是采用组合逻辑的方式产生的。 以上的touch_flag是采用时序逻辑产生的,时序逻辑会延迟一拍。 以上是上升沿和下降沿的组合逻辑和时序逻辑实现,逻辑或…...

QT自定义控件0-360°刻度尺
支持0到360,360到0的过度。 直接上代码,可以直接用,使用的paintevent事件实现的,没啥好讲的。 .cpp void Widget::drawCourse(QPainter& p,QPen pen,QFont font) {double currentNumber m_ang;p.setBrush(Qt::black);p.dra…...
c语言0基础笔记
目录 前言 第01章_C语言入门 1.1初识计算机语言 1.2初识C语言 1.3第一个c程序 1.4IDE使用 1.5注释 1.6第一个c程序剖析 1.7printf()输出格式 第02章_变量与进制 2.1关键字 2.2标识符 2.3变量 2.4基本数据类型的使用 2.5变量间的运算规则 2.6常量 2.7输入/输出函…...
Vue 中 Element UI 的 el-table 组件实现动态表头和内容
在 Vue 中使用 Element UI 的 el-table 组件时,为了实现动态表头(包括第一层表头及其下的嵌套表头或子表头)。需要后端返回的数据结构能够体现表头层级关系以及对应的数据结构相匹配。这样的数据通常是一个嵌套数组,每个表头单元可…...

安装sqlserver后—无法连接到 127.0.0.1,1433\sqlexpress
报错问题如下: 标题: 连接到服务器 ------------------------------ 无法连接到 127.0.0.1,1433\sqlexpress。 ------------------------------ 其他信息: 登录失败。该登录名来自不受信任的域,不能与 Windows 身份验证一起使用。 (Microsoft SQL Serve…...
Python JSON解析校验格式,输出错误信息的工具
引言: 在现代软件开发中,JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,被广泛应用于前后端数据传输和存储。然而,由于JSON的灵活性和复杂性,解析JSON时常常会遇到格式错误的问…...

物联网网关与plc怎么连接?
物联网网关与plc怎么连接? 物联网是当今社会中最热门的技术之一,而物联网网关则是连接物联网设备与云平台的核心设备之一。物联网网关在连接各种传感器和设备时起着至关重要的作用。而另一种广泛应用于工业控制和自动化领域的设备是可编程逻辑控制器&…...

HANA:存储过程(Procedures) DEBUG
作者 idan lian 如需转载备注出处 如果对你有帮助,请点赞收藏~~~ 1.场景 最近不是写了蛮多hana的存储过程吗,如果是简单的增删改查,如果结果错了,还是比较容易找到错误在哪的,但是逐渐假如循环啊,变量判…...

Oracle行转列函数,列转行函数
Oracle行转列函数,列转行函数 Oracle 可以通过PIVOT,UNPIVOT,分解一行里面的值为多个列,及来合并多个列为一行。 PIVOT PIVOT是用于将行数据转换为列数据的查询操作(类似数据透视表)。通过使用PIVOT,您可以按照特定的列值将数据进行汇总,并将…...

线程同步--生产者消费者模型
文章目录 一.条件变量pthread线程库提供的条件变量操作 二.生产者消费者模型生产者消费者模型的高效性基于环形队列实现生产者消费者模型中的数据容器 一.条件变量 条件变量是线程间共享的全局变量,线程间可以通过条件变量进行同步控制条件变量的使用必须依赖于互斥锁以确保线…...
React hook+AntD pro实现Form表单的二次封装
React hookAntD pro实现Form表单的二次封装 封装Form表单1、在src/types下新建 antd/form/index.ts,进行Form表的配置、数据等类型的限制2、在 根目录/components 下新建 BaseForm/index.tsx文件3、在BaseForm/createFormIpt.tsx中,抽取对不同类型的表单…...
python异步切片下载文件(内置redis获取任务 mongo更新任务状态等)
异步切片下载二进制文件并上传桶删除本地文件 import json import os import asyncio from urllib import parseimport aiohttp import aioredis from motor.motor_asyncio import AsyncIOMotorClient from retrying import retry from minio import Minio from minio.error im…...

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(10)-Fiddler如何设置捕获Firefox浏览器的Https会话
1.简介 经过上一篇对Fiddler的配置后,绝大多数的Https的会话,我们可以成功捕获抓取到,但是有些版本的Firefox浏览器仍然是捕获不到其的Https会话,需要我们更进一步的配置才能捕获到会话进行抓包。 2.宏哥环境 1.宏哥的环境是Wi…...
在rocky linux 9.5上在线安装 docker
前面是指南,后面是日志 sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo dnf install docker-ce docker-ce-cli containerd.io -y docker version sudo systemctl start docker sudo systemctl status docker …...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...

内窥镜检查中基于提示的息肉分割|文献速递-深度学习医疗AI最新文献
Title 题目 Prompt-based polyp segmentation during endoscopy 内窥镜检查中基于提示的息肉分割 01 文献速递介绍 以下是对这段英文内容的中文翻译: ### 胃肠道癌症的发病率呈上升趋势,且有年轻化倾向(Bray等人,2018&#x…...

结构化文件管理实战:实现目录自动创建与归类
手动操作容易因疲劳或疏忽导致命名错误、路径混乱等问题,进而引发后续程序异常。使用工具进行标准化操作,能有效降低出错概率。 需要快速整理大量文件的技术用户而言,这款工具提供了一种轻便高效的解决方案。程序体积仅有 156KB,…...