当前位置: 首页 > news >正文

Java设计模式之《亨元模式》

目录

1、亨元模式

1.1、定义

1.2、角色

1.3、优缺点

1.4、使用场景

2、实现


前言

关于Java的设计模式分类如下:

关于亨元模式的结构如下:

享元模式 = “以时间换空间” 或 “以计算换内存” —— 少创建对象,多复用。


1、亨元模式

1.1、定义

享元模式 是一种 以共享对象的方式减少内存使用对象创建开销 的设计模式。

它的核心思想是:

🔁 “共享不变的部分,分离变化的部分”
✅ 把大量重复的、不变的状态提取出来,供多个对象共享,从而节省内存。

1.2、角色

享元模式包含以下几个核心角色:

1、抽象亨元(Flyweight)

        抽象亨元定义了一个接口,用于接收外部状态并执行相应的操作。所有具体亨元类都必须实现该接口,确保客户端可以以统一的方式操作不同的亨元对象。

2、具体亨元(Concrete Flyweight)

        具体亨元是抽象亨元的实现类,它包含了对象的内部状态,并实现了抽象亨元接口中定义的方法。具体亨元对象可以被共享,在不同的场景中重复使用。

3、亨元工厂(Flyweight Factory)

        亨元工厂负责创建和管理亨元对象。它维护一个亨元对象池(通常使用集合实现),当客户端请求亨元对象时,工厂会先检查对象池中是否存在符合条件的对象,如果存在则直接返回,否则创建一个新的亨元对象并放入池中,以供后续复用。

4、客户端(Client)

        客户端负责创建和维护外部状态,并调用亨元工厂获取亨元对象,将外部状态传递给亨元对象进行操作。客户端不直接创建具体亨元对象,而是通过亨元工厂获取共享的亨元对象。​

1.3、优缺点

如下所示:

1.4、使用场景

        当有 大量相似对象,且这些对象中包含 大量重复的、可共享的数据 时,就可以用享元模式。

常见例子:

  • 文本编辑器中的字符样式(字体、颜色、大小)——成千上万个字符,但样式种类有限
  • 游戏中的子弹、敌人、树木——外观相同,位置不同
  • 数据库连接池线程池(虽然不完全是享元,但思想类似)

Java 标准库中也有享元思想的应用,如下所示:

1、String的字符串常量池

String a = "hello";
String b = "hello";
System.out.println(a == b); // true!
因为 "hello" 这个字符串只存了一份,在 字符串常量池 中被共享了。

2、Integer的cache

  • Integer:-128 ~ 127 缓存
  • Boolean.TRUE / Boolean.FALSE:单例
  • Byte、Short、Character:也有类似缓存

更多的可参考:关于IntegerCache.cache的介绍-CSDN博客文章浏览阅读1.1k次,点赞27次,收藏25次。127 在缓存范围内a和b都指向,因此a == b为true。128 超出缓存范围a和b会分别创建两个新对象,因此a == b为false。// 127 在缓存范围内// true(a 和 b 指向方法区的同一个对象)// 128 超出缓存范围// false(c 和 d 指向堆中的不同对象)小结小结。_integercache.cache https://dyclt.blog.csdn.net/article/details/148062599?spm=1011.2415.3001.5331

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true(享元)Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(c == d); // false(超出缓存范围)

3、枚举本身

public enum Color {RED, GREEN, BLUE;
}
✅ Color.RED 是唯一的、全局共享的实例,不会重复创建。

4、日志框架中的 Logger 获取

Logger logger1 = LoggerFactory.getLogger(UserService.class);
Logger logger2 = LoggerFactory.getLogger(UserService.class);
System.out.println(logger1 == logger2); // true

5、数据库连接池 / 线程池 —— 资源复用

虽然不完全等同于享元,但思想一致:复用昂贵资源

// 连接池(如 HikariCP)
DataSource dataSource = ... // 配置好的连接池Connection conn1 = dataSource.getConnection(); // 不是 new,是从池里拿
Connection conn2 = dataSource.getConnection();
// 用完后归还,不是关闭✅ 这就是“享元”思想:连接对象被共享复用
避免频繁创建和销毁(TCP 连接很贵)
池子就是“享元工厂”


2、实现

举个例子:咖啡馆的“饮品机” 🧋

想象你开了一家奶茶店,每天有 1000 个顾客 来买奶茶。

每杯奶茶都有:

  • 口味(如:珍珠奶茶、椰果奶茶、布丁奶茶)✅ → 这个是固定的、可重复的
  • 顾客名字(如:小明、小红)❌ → 这个是变化的、每个人的

❌ 不用享元模式:每杯奶茶都“全新制作”(浪费!)

// 每次都 new 一个新对象,即使口味一样
new MilkTea("珍珠奶茶", "小明");
new MilkTea("珍珠奶茶", "小红");
new MilkTea("珍珠奶茶", "小刚");
// ... 重复 100 次

💡 问题:做了 100 杯“珍珠奶茶”,但“珍珠奶茶”这个配方被重复创建了 100 次 —— 浪费!

用享元模式:共享“配方”,只变“名字”

把 口味(配方) 提取出来,做成“共享的”,只保存一份。

代码示例如下:

// 1. 共享的“口味”(内部状态)——只创建一次
class Flavor {private final String name;public Flavor(String name) {this.name = name;System.out.println("🔥 创建新口味:" + name);}public void serve(String customerName) {System.out.println("🎉 给 " + customerName + " 做了一杯 " + name);}
}// 2. 工厂:确保每种口味只创建一次
class FlavorFactory {private static Map<String, Flavor> flavors = new HashMap<>();public static Flavor getFlavor(String name) {// 如果没有,就创建;如果有,就返回已有的return flavors.computeIfAbsent(name, Flavor::new);}
}

🧑‍🍳 客户端使用(点单)

public class CoffeeShop {public static void main(String[] args) {// 10 个顾客要买奶茶String[] customers = {"小明", "小红", "小刚", "小丽", "小强", "小芳", "小军", "小美", "小亮", "小霞"};for (String customer : customers) {// 点“珍珠奶茶”Flavor flavor = FlavorFactory.getFlavor("珍珠奶茶");flavor.serve(customer);}}
}

✅ 输出结果:

🔥 创建新口味:珍珠奶茶
🎉 给 小明 做了一杯 珍珠奶茶
🎉 给 小红 做了一杯 珍珠奶茶
🎉 给 小刚 做了一杯 珍珠奶茶
...
🎉 给 小霞 做了一杯 珍珠奶茶

✅ “珍珠奶茶”这个口味只创建了一次
✅ 但服务了 10 个不同顾客(外部状态:名字)
✅ 节省了 9 次对象创建!

小结:

“配方”是共享的(享元),“名字”是每次传进来的(外部状态)。

就像一台奶茶机:

  • 配方(口味)是固定的 → 共享
  • 顾客名字每次不同 → 由你传进来

总结

        亨元设计模式通过对象共享和状态分离的巧妙设计,为处理大量相似对象提供了高效的解决方案,在优化内存占用提升系统性能方面具有显著优势。然而,其复杂的结构和外部状态管理要求也限制了它的适用范围

  


参考文章:

1、结构型模式-亨元模式-CSDN博客文章浏览阅读1k次,点赞28次,收藏20次。亨元模式属于结构型设计模式,通过共享对象来减少内存中对象的数量,优化资源使用。该模式将对象状态分为内部状态(可共享)和外部状态(需外部传入),亨元工厂负责创建和管理共享对象,客户端需自行处理外部状态。其核心是数据共享与状态分离,避免重复创建相似对象。适用于存在大量相似对象、且对象状态可拆分的场景(如文本渲染、游戏对象池),能显著降低内存占用,提升系统性能,同时保持对象操作的透明性。 https://blog.csdn.net/y245166349/article/details/148429231?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522b4fc30ca4840a1e6f2a64c7909d738a1%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=b4fc30ca4840a1e6f2a64c7909d738a1&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-148429231-null-null.142^v102^control&utm_term=%E4%BA%A8%E5%85%83%E6%A8%A1%E5%BC%8F&spm=1018.2226.3001.4187

http://www.dtcms.com/a/353590.html

相关文章:

  • HttpRequest.get()方法报错:301 Moved Permanently
  • XFile v2 系统架构文档
  • Unity List 相关
  • QOpenGLFunctions_2_1 与 OpenGL 的区别
  • 【系统架构设计(四)】软件工程:从瀑布到敏捷的演进之路
  • 【系统架构设计(三)】系统工程与信息系统基础下:企业信息化与电子商务-数字化转型的核心驱动力
  • 【Django + Pure Admin】基于Django+Vue3的前后端分离管理系统框架设计
  • 服务器硬件电路设计之 SPI 问答(六):如何提升服务器硬件电路中的性能?如何强化稳定性?
  • MySQL explain命令的作用
  • 什么是AI+?什么是人工智能+?
  • 济南大学杨波与济南青盟信息技术有限公司杨华伟
  • 北京国标竞品调查,知己知彼(竞品调查研究)
  • Java全栈开发面试实战:从基础到微服务的深度探索
  • Linux学习-TCP并发服务器构建
  • XState
  • 第五章:循环
  • Playwright之脱离元素,页面操作大全!
  • 2026 年美国国际太阳能展(RE+)
  • 如何在 Docker 和AKS上使用 IIS
  • 【Redis 进阶】Redis 典型应用 —— 分布式锁
  • F008 vue+flask 音乐推荐评论和可视化系统+带爬虫前后端分离系统
  • Android中APK包含哪些内容?
  • k8s集群Prometheus部署
  • 【Python办公】快速比较Excel文件中任意两列数据的一致性
  • 【Rust】 1. 变量学习笔记
  • DWT域进行视频信息隐藏的原理及优缺点
  • 洞悉Oracle数据库的基石:深入剖析其核心物理存储结构
  • 2025软件测试面试八股文(完整版)
  • 【Redis 进阶】Redis 典型应用 —— 缓存(cache)
  • day2_softmax回归的实现 李沐动手学深度学习pytorch记录