肝一肝设计模式【九】-- 享元模式
系列文章目录
肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门
肝一肝设计模式【七】-- 代理模式 传送门
肝一肝设计模式【八】-- 外观模式 传送门
文章目录
- 系列文章目录
- 前言
- 一、什么是享元模式
- 二、享元模式中的角色
- 三、举个栗子
- 四、在开源框架中的使用
- 写在最后
前言
本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式、装饰器模式、代理模式、外观模式,本节我们来学习一下——享元模式。
一、什么是享元模式
享元模式(Flyweight Pattern),它旨在减少对象的内存消耗,提高应用程序的性能。该模式通过共享尽可能多的细粒度对象来实现这一目标,从而有效地支持大量细粒度的对象。
在享元模式中,对象分为两种类型:内部状态(Intrinsic State)和外部状态(Extrinsic State)。内部状态是对象可以共享的状态,不依赖于对象的上下文,而外部状态是对象特定的、依赖于对象的上下文的状态。
享元模式的核心思想是将对象的状态划分为内部状态和外部状态,并且通过共享内部状态来减少对象的数量。
二、享元模式中的角色
享元模式包含以下几个角色:
- 享元接口(Flyweight):定义享元对象的接口,通过该接口可以接收和操作外部状态。
- 具体享元(ConcreteFlyweight):实现享元接口,包含内部状态,可以被共享和复用。具体享元对象需要注意线程安全的处理。
- 享元工厂(FlyweightFactory):负责创建和管理享元对象,它维护一个享元池(Flyweight Pool),用于存储和复用已经创建的享元对象。当客户端请求一个享元对象时,享元工厂会检查享元池中是否已经存在该对象,如果存在则直接返回,否则创建一个新的享元对象并放入享元池中。
- 客户端(Client):使用享元模式的对象,通过享元工厂获取享元对象,并根据需要传递外部状态。
三、举个栗子
最近在玩王国之泪,咱们就拿游戏来举个例子。ps:塞尔达是天,任天堂是宇宙的主宰!

在游戏中我们会遇到很多怪物,如果创建每个怪物都包含大量的数据(例如位置、外观、属性等),将会占用大量的内存。
而享元模式可以帮助我们减少内存消耗,提高性能。
上代码:
import java.util.HashMap;
import java.util.Map;// 怪物接口
interface Monster {void display();
}// 具体怪物类
class ConcreteMonster implements Monster {private String type;private String image; // 内部状态:怪物图片public ConcreteMonster(String type) {this.type = type;// 加载怪物图片,这里简化为字符串this.image = "Image_" + type;}public void display() {System.out.println("Type: " + type + ", Image: " + image);}
}// 怪物工厂
class MonsterFactory {private static Map<String, Monster> monsterMap = new HashMap<>();public static Monster getMonster(String type) {if (monsterMap.containsKey(type)) {return monsterMap.get(type);} else {Monster monster = new ConcreteMonster(type);monsterMap.put(type, monster);return monster;}}
}// 客户端代码
public class Client {public static void main(String[] args) {Monster monster1 = MonsterFactory.getMonster("波克布林");Monster monster2 = MonsterFactory.getMonster("丘丘");Monster monster3 = MonsterFactory.getMonster("波克布林");Monster monster4 = MonsterFactory.getMonster("丘丘");monster1.display(); // Type: 波克布林, Image: Image_波克布林monster2.display(); // Type: 丘丘, Image: Image_丘丘monster3.display(); // Type: 波克布林, Image: Image_波克布林monster4.display(); // Type: 丘丘, Image: Image_丘丘System.out.println("monster1 == monster3: " + (monster1 == monster3)); // true,内部状态相同的对象被共享System.out.println("monster2 == monster4: " + (monster2 == monster4)); // true,内部状态相同的对象被共享}
}
在上述示例中,我们定义了一个怪物接口 Monster,并实现了具体的怪物类 ConcreteMonster。ConcreteMonster 类包含了怪物的类型和内部状态 image,在构造函数中根据类型加载怪物图片。
然后,我们创建了怪物工厂 MonsterFactory,通过该工厂可以获取怪物对象。在工厂中,我们使用一个 monsterMap 来缓存已创建的怪物对象。如果请求的怪物对象已存在于缓存中,则直接返回;否则,创建一个新的怪物对象并存入缓存。
在客户端代码中,我们通过怪物工厂获取了不同类型的怪物对象,并调用 display() 方法展示怪物的信息。注意,相同类型的怪物对象被共享使用,因为它们具有相同的内部状态。
四、在开源框架中的使用
数据库连接池就是一个常见的应用享元模式的例子,其中HikariCP 是一个轻量级、高性能的数据库连接池框架,它在内部使用了享元模式来管理连接对象的复用。
在 HikariCP 的源码中,核心的享元模式应用是在 HikariPool 类中,它是连接池的主要实现。下面是一个简化的代码示例,展示了 HikariPool 中享元模式的部分实现:
public class HikariPool {// 内部状态:连接对象池private ConcurrentBag<PoolEntry> poolEntries;// ...public Connection getConnection() throws SQLException {// 从连接池中获取一个可用的连接PoolEntry entry = poolEntries.borrow(timeoutMs);return wrapConnection(entry);}private Connection wrapConnection(PoolEntry entry) {// 对连接对象进行包装,添加上下文信息Connection connection = entry.connection;// ...return connection;}// ...private static class PoolEntry {// 内部状态:数据库连接对象final Connection connection;// 外部状态:是否被占用volatile boolean isMarkedInUse;// ...PoolEntry(Connection connection) {this.connection = connection;}// ...}
}
在上述示例中,HikariPool 类表示连接池的主要实现。poolEntries 是连接对象池,其中的每个元素 PoolEntry 表示一个连接对象。PoolEntry 类中的 connection 属性表示数据库连接对象,而 isMarkedInUse 属性表示连接是否被占用。
当调用 getConnection() 方法时,HikariPool 会从连接池中获取一个可用的连接对象,并调用 wrapConnection() 方法对连接进行包装,添加上下文信息等处理。
在 PoolEntry 类中,connection 属性表示数据库连接对象,而 isMarkedInUse 属性用于标记连接是否被占用。这个外部状态的管理使得连接对象可以在不同的上下文中被复用。
由于源码非常复杂,上述示例只是简化了部分代码,真实的 HikariCP 源码中还包含了许多其他功能和优化策略。
写在最后
享元模式可以有效地节省内存空间,特别是在需要创建大量细粒度对象时。通过共享内部状态,可以减少对象的数量,提高应用程序的性能。然而,享元模式的使用需要权衡内部状态和外部状态的划分,以及对线程安全的处理,因为共享的对象可能被多个线程同时访问。
享元模式的优点:
- 内存优化:享元模式通过共享对象的方式减少了内存的使用。相同的对象只在内存中存储一份,多个相似的对象可以共享相同的状态,从而减少了对象的数量和内存占用。
- 性能提升:由于享元模式减少了对象的数量,从而减少了对象的创建和销毁的开销,提高了系统的性能。特别是在需要大量创建对象的场景下,享元模式可以显著减少系统的开销。
- 状态外部化:享元模式将对象的内部状态和外部状态分离,内部状态共享,外部状态由客户端管理。这样可以简化对象的状态管理,提高系统的可维护性和灵活性。
享元模式的缺点:
- 对象共享可能引发线程安全问题:由于多个线程共享相同的对象,可能需要额外的同步措施来保证线程安全性。在多线程环境下,需要注意对共享对象的访问控制,以避免并发问题。
- 对象共享可能导致代码复杂性增加:享元模式需要对对象的内部状态和外部状态进行区分和管理,可能会增加代码的复杂性。这需要在设计和实现过程中进行权衡和折衷。
相关文章:
肝一肝设计模式【九】-- 享元模式
系列文章目录 肝一肝设计模式【一】-- 单例模式 传送门 肝一肝设计模式【二】-- 工厂模式 传送门 肝一肝设计模式【三】-- 原型模式 传送门 肝一肝设计模式【四】-- 建造者模式 传送门 肝一肝设计模式【五】-- 适配器模式 传送门 肝一肝设计模式【六】-- 装饰器模式 传送门 肝…...
自动化测试的十大雷区【刚入门必看】
虽然从自己的错误中学习也不错,但从别人的错误中学习总是更好的。 作为一个自动化测试人员,分享常见的容易犯的10个错误,可以从中吸取教训,引以为鉴。 一、必要时才自动化 新人小王接到为Web应用程序自动化测试脚本的任务时&…...
【Android源码篇】用grep搜索源码内容关键词
前言 选项: • -w:只匹配整个单词,不会部分匹配 • -r:递归搜索 • -n:显示行号 • -i:忽略字符大小写 • -I(大写i):忽略二进制文件 • -I:忽略文件内容&am…...
腾讯云轻量应用服务器卡死怎么连接?
腾讯云轻量云服务器卡死怎么解决?使用腾讯云自带的VNC登录连接轻量服务器,或使用腾讯云OrcaTerm一键免密登录轻量实例。如果是确定数据没问题,也可以使用控制台自带的重启实例。 腾讯云轻量应用服务器参考:https://curl.qcloud.co…...
Charles安装及抓取APP接口
一、Charles使用 Charles是一款代理服务器,通过过将自己设置成系统(电脑或者浏览器)的网络访问代理服务器,然后截取请求和请求结果达到分析抓包的目的。该软件是用Java写的,能够在Windows,Mac,…...
Linux开发工具:yum和vim的使用
目录 一. Linux下的软件 1.1 软件安装的三种方法 1.2 采用yum安装软件 1.3 yum源的问题 二. vim开发工具的使用 2.1 vim的三种基本模式 2.2 命令模式下vim的常用指令 2.2.1 定位相关指令 2.2.2 光标移动相关指令 2.2.3 插入相关指令 2.2.4 复制粘贴相关指令 2.2.5 替…...
Java基础重温巩固
方法 方法与方法之间是平级关系,不能嵌套return表示结束当前方法 基本数据类型和引用数据类型 基本数据类型:数据存储在自己的空间中 引用数据类型:数据存储在其他空间中,自己空间存储的是地址值 值传递 传递基本数据类型时&…...
Text2SQL 语义解析数据集、解决方案和学术论文资源整合
目录 什么是Text2SQL? Text2SQL语义解析数据集 Text2SQL解决方案 Text2SQL相关学术论文 欢迎大家,我是你们的博主,今天我们来讨论一个非常有趣且有挑战性的话题 —— Text2SQL。这个话题涉及到自然语言处理 (NLP),数据库查询语言 (SQL)&…...
redis集群+哨兵配置实操宝典
本地安装redis 配置集群和哨兵 1、下载安装redis #wget http://download.redis.io/releases/redis-5.0.12.tar.gz #下载安装包 #yum -y install gcc #安装依赖包 #tar -zxvf redis-5.0.12.tar.gz #cd redis-5.0.12 #make 2、主备配置 我们采用一主两备的结构 主机 192.168.3.…...
nginx的语法
概览 Nginx是一个高效、稳定的开源Web服务器和反向代理服务器,也可以用作邮件代理服务器、负载均衡器和HTTP缓存。以下是Nginx配置文件的一些基本语法和组成部分: 配置块(Block Directives):Nginx配置文件由许多嵌套的…...
华为OD机试之英文输入法(Java源码)
英文输入法 题目描述 主管期望你来实现英文输入法单词联想功能。 需求如下: 依据用户输入的单词前缀,从已输入的英文语句中联想出用户想输入的单词,按字典序输出联想到的单词序列, 如果联想不到,请输出用户输入的单词…...
一个团队管理者应该干什么?
文章目录 一、前言二、搞好团队气氛三、上下都要处理好四、做好计划并监督执行,控制风险。五、小结 一、前言 话说管理这个东西是猪有猪的想法,狗有狗的想法。所以不会有一个定论,总是有人定义这个管理方式,那个管理方式。看的管…...
服务器数据库文件加载到 MySQL
要将数据库文件加载到 MySQL 中,您可以使用以下步骤: 1. 确保 MySQL 服务器正在运行。您可以使用以下命令检查 MySQL 服务器的状态: sudo systemctl status mariadb 如果 MySQL 服务器没有运行,请使用以下命令启动它&…...
6-《网络面试》
6-《网络面试》 1.http是什么?http的工作机制?http报文?1.1 http工作机制:1.2 URL和http报文 2. HTTP请求方法和状态码3.Get和Post的区别4.HTTP的Header解析1.text/html2.x-www-form-urlencoded3.multipart/form-data4.applicatio…...
[高光谱]高光谱数据的获取与展示
一、环境准备 需要安装spectral包,这个包专门用于高光谱数据展示。 pip install spectral 二、数据加载 要预先准备原始高光谱的.mat数据和分类数据gt.mat(ground-turth);然后使用scipy.io中的loadmat(.)将其读入程序。 from scipy.io import loadmat…...
veth网卡的多队列及RPS
背景: 3.10内核下容器使用的veth网卡,默认开启的是一个队列,导致在某些单线程多TCP链接的应用场景下,出现某个CPU软中断高的情况。之前处理的方案一直是开启这个veth网卡的RPS,让其在多流场景下可以去分散到其它CPU上…...
国内的程序员数量是否已经饱和或者过剩?
首先,国内程序员数量确实在逐年增加,特别是近年来互联网行业迅猛发展,促进了技术人员需求的增长。然而,要判断程序员是否饱和并不是简单地看人数。下面我们细分几个角度来看看这个问题。 1、合格的程序员数量不够 国内的IT领域和…...
flutter不能抓包
需要获取手机IP地址设置才能抓包,获取IP地址,需要跟原生通讯获取, 1:获取IP地址 安卓代码: /*** 原生和flutter通讯交互*/ class MainActivity : FlutterActivity() {var methodChannel: MethodChannel? nullover…...
从桌面端到移动端,.NET MAUI为什么对WPF开发人员更简单?
.NET多平台应用程序UI(. NET MAUI)的市场吸引力与日俱增,这是微软最新的开发平台,允许开发者使用单个代码库创建跨平台应用程序。尽管很多WPF开发人员还没有跟上 .NET MAUI的潮流,但我们将在这篇文章中为大家展示他的潜…...
[Python]... 和pass
2.9 … 和pass 在Python中pass语句是用来在语法上需要一个语句的地方占位的。它不会执行任何操作。 ...是一个特殊的值,表示省略或未完成的代码。它可以用来实现抽象基类,也可以用来在语法上需要一个语句的地方占位,通常情况下,.…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
JS手写代码篇----使用Promise封装AJAX请求
15、使用Promise封装AJAX请求 promise就有reject和resolve了,就不必写成功和失败的回调函数了 const BASEURL ./手写ajax/test.jsonfunction promiseAjax() {return new Promise((resolve, reject) > {const xhr new XMLHttpRequest();xhr.open("get&quo…...
Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
[USACO23FEB] Bakery S
题目描述 Bessie 开了一家面包店! 在她的面包店里,Bessie 有一个烤箱,可以在 t C t_C tC 的时间内生产一块饼干或在 t M t_M tM 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC,tM≤109)。由于空间…...
前端调试HTTP状态码
1xx(信息类状态码) 这类状态码表示临时响应,需要客户端继续处理请求。 100 Continue 服务器已收到请求的初始部分,客户端应继续发送剩余部分。 2xx(成功类状态码) 表示请求已成功被服务器接收、理解并处…...
