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

设计模式(十二)结构型:享元模式详解

设计模式(十二)结构型:享元模式详解

享元模式(Flyweight Pattern)是 GoF 23 种设计模式中的结构型模式之一,其核心价值在于通过共享大量细粒度对象来有效支持大规模对象的创建与管理,从而显著减少内存占用和系统开销。它适用于系统中存在大量相似对象的场景,通过分离“可变的外部状态”与“不可变的内部状态”,使多个对象可以共享相同的内部状态实例,从而实现资源的高效复用。享元模式是性能优化的关键手段,广泛应用于文本编辑器(字符格式)、图形系统(图标、样式)、游戏开发(粒子系统、NPC 模板)、数据库连接池、线程池等需要管理大量轻量级对象的系统中。

一、详细介绍

享元模式解决的是“对象数量过多导致内存溢出或性能下降”的问题。在某些应用中,可能需要创建成千上万甚至上百万个对象,如文档中的每个字符、地图上的每个图元、游戏中的每个子弹。如果每个对象都独立存储其全部状态,内存消耗将极其巨大。

享元模式的核心思想是:“共享”而非“重复创建”。它将对象的状态分为两类:

  • 内部状态(Intrinsic State):存储在享元对象内部,不随环境变化,可以被多个对象共享。例如,字符的字体、字号、颜色;图形的形状、线型;游戏单位的模型、攻击力等。
  • 外部状态(Extrinsic State):依赖于上下文,随环境变化,不能被共享,必须由客户端在运行时传入。例如,字符在文档中的位置;图形的坐标;游戏单位的当前血量、位置等。

通过将内部状态提取出来并共享,而将外部状态由客户端管理并在调用时传入,享元模式实现了对象的“池化”管理。系统不再为每个逻辑对象创建一个完整实例,而是从“享元池”中获取共享的内部状态对象,并结合外部状态完成操作。

该模式包含以下核心角色:

  • Flyweight(享元接口):定义享元对象对外提供的操作接口,通常包含一个方法接收外部状态作为参数。
  • ConcreteFlyweight(具体享元类):实现 Flyweight 接口,存储内部状态。它是可共享的,通常设计为不可变对象(immutable)以保证线程安全。
  • UnsharedConcreteFlyweight(非共享具体享元):某些情况下,并非所有对象都适合共享,此类对象不参与共享机制,但实现相同接口。
  • FlyweightFactory(享元工厂):负责管理享元对象的创建与共享。它通常使用集合(如 Map)缓存已创建的享元对象,确保相同内部状态只创建一次。客户端通过工厂获取享元实例。
  • Client(客户端):维护外部状态,并在需要时从工厂获取享元对象,然后将外部状态传递给享元对象的方法以完成操作。

享元模式的关键优势:

  • 节省内存:大量对象共享内部状态,显著减少实例数量。
  • 提升性能:减少对象创建与垃圾回收开销。
  • 支持大规模系统:使处理海量对象成为可能。

与“对象池模式”相比,享元关注状态分离与共享,对象池关注对象生命周期管理;享元通常用于不可变状态的共享,而对象池用于可重用对象(如数据库连接)的复用。

二、享元模式的UML表示

以下是享元模式的标准 UML 类图:

implements
implements
creates and manages
uses
uses
«interface»
Flyweight
+operation(extrinsicState: Object)
ConcreteFlyweight
-intrinsicState: String
+operation(extrinsicState: Object)
UnsharedConcreteFlyweight
+operation(extrinsicState: Object)
FlyweightFactory
-flyweights: Map<String, Flyweight>
+getFlyweight(key: String)
Client
-extrinsicState: Object
+doWork()

图解说明

  • Flyweight 接口定义操作方法,接收外部状态。
  • ConcreteFlyweight 存储内部状态(如 intrinsicState),实现 operation()
  • FlyweightFactory 使用 Map 缓存享元对象,getFlyweight(key) 确保唯一实例。
  • Client 持有外部状态,在调用时传入享元对象。
  • 客户端通过工厂获取享元,结合外部状态完成操作。

三、一个简单的Java程序实例及其UML图

以下是一个文本编辑器中字符渲染的示例,展示如何使用享元模式共享字符的字体、颜色等格式信息。

Java 程序实例
import java.util.HashMap;
import java.util.Map;// 享元接口:字符格式
interface CharacterFormat {void display(String content, int x, int y);
}// 具体享元类:共享的字符格式(内部状态)
class SharedCharacterFormat implements CharacterFormat {private final String font;private final int size;private final String color;// 构造时初始化内部状态,且不可变public SharedCharacterFormat(String font, int size, String color) {this.font = font;this.size = size;this.color = color;}// 外部状态(content, x, y)由客户端传入@Overridepublic void display(String content, int x, int y) {System.out.printf("🖋️ Render '%s' at (%d,%d) | Font: %s, Size: %d, Color: %s\n",content, x, y, font, size, color);}// 用于工厂中作为缓存的 keypublic String getKey() {return font + "-" + size + "-" + color;}
}// 享元工厂:管理字符格式的共享实例
class CharacterFormatFactory {private static final Map<String, CharacterFormat> pool = new HashMap<>();public static CharacterFormat getFormat(String font, int size, String color) {String key = font + "-" + size + "-" + color;return pool.computeIfAbsent(key, k -> new SharedCharacterFormat(font, size, color));}public static int getPoolSize() {return pool.size();}
}// 客户端:文本编辑器
public class FlyweightPatternDemo {public static void main(String[] args) {System.out.println("📝 文本编辑器使用享元模式渲染字符\n");// 模拟文档中多个字符的渲染// 字符 'H' 在位置 (0,0)CharacterFormat format1 = CharacterFormatFactory.getFormat("Arial", 12, "black");format1.display("H", 0, 0);// 字符 'e' 在位置 (10,0),使用相同格式CharacterFormat format2 = CharacterFormatFactory.getFormat("Arial", 12, "black");format2.display("e", 10, 0);// 字符 'l' 在位置 (20,0),使用相同格式CharacterFormat format3 = CharacterFormatFactory.getFormat("Arial", 12, "black");format3.display("l", 20, 0);// 字符 'W' 在位置 (0,20),使用不同格式CharacterFormat format4 = CharacterFormatFactory.getFormat("Times New Roman", 14, "blue");format4.display("W", 0, 20);// 字符 'o' 在位置 (30,0),使用原始格式CharacterFormat format5 = CharacterFormatFactory.getFormat("Arial", 12, "black");format5.display("o", 30, 0);// 验证共享:format1, format2, format3, format5 应为同一实例System.out.println("\n✅ 享元池中唯一格式实例数量: " + CharacterFormatFactory.getPoolSize());System.out.println("🔍 format1 == format2: " + (format1 == format2));System.out.println("🔍 format2 == format5: " + (format2 == format5));System.out.println("🔍 format1 == format4: " + (format1 == format4));System.out.println("\n💡 结论:相同格式被共享,不同格式独立创建。");}
}
实例对应的UML图(简化版)
implements
creates and manages
uses
uses
«interface»
CharacterFormat
+display(content: String, x: int, y: int)
SharedCharacterFormat
-font: String
-size: int
-color: String
+display(content: String, x: int, y: int)
+getKey()
CharacterFormatFactory
-pool: Map<String, CharacterFormat>
+getFormat(font: String, size: int, color: String)
+getPoolSize()
Client
+main(args: String[])

运行说明

  • SharedCharacterFormat 存储字体、大小、颜色等内部状态,不可变。
  • CharacterFormatFactory 使用 Map 缓存享元对象,确保相同格式只创建一次。
  • 客户端调用 getFormat() 获取享元,并传入字符内容和坐标(外部状态)进行渲染。
  • 输出显示:多个字符共享同一格式实例,享元池大小远小于字符数量。

四、总结

特性说明
核心目的共享细粒度对象,减少内存占用
实现机制分离内部状态(共享)与外部状态(客户端传入)
优点显著节省内存、减少对象创建开销、支持大规模对象系统
缺点增加系统复杂性、需仔细划分内外状态、可能引入线程安全问题
适用场景大量相似对象(字符、图形、粒子)、状态可分离、内部状态为主
不适用场景对象数量少、状态难以分离、外部状态占比大、需要频繁修改内部状态

享元模式使用建议

  • 内部状态应设计为不可变对象,确保线程安全。
  • 享元工厂通常使用单例模式实现。
  • 外部状态应尽量轻量,避免成为新的性能瓶颈。
  • 在 Java 中,String 常量池、Integer.valueOf() 缓存(-128~127)是享元思想的体现。

架构师洞见:
享元模式是“空间换时间”与“状态管理”的高级应用。在现代系统中,其思想已扩展到缓存设计数据压缩虚拟化技术中。例如,在前端框架中,React 的 key 机制和虚拟 DOM diff 算法隐含了享元思想——复用 DOM 节点而非重建;在游戏引擎中,ECS(实体-组件-系统)架构通过共享组件数据实现高效渲染;在大数据处理中,列式存储通过共享重复值实现压缩。

未来趋势是:享元模式将与持久化数据结构结合,在函数式编程中实现高效共享;在AI 推理中,模型权重作为“内部状态”被多个推理请求共享;在元宇宙3D 渲染中,海量图元和材质将依赖享元机制实现流畅交互。

掌握享元模式,有助于设计出高效、可扩展、资源友好的系统。作为架构师,应在识别到“海量相似对象”时,主动引入享元模式进行优化。享元不仅是模式,更是资源管理的哲学——它教会我们:真正的效率,来自于对“共享”与“分离”的深刻洞察。

http://www.xdnf.cn/news/16570.html

相关文章:

  • Python day26
  • 无向图的连通性问题
  • 设计模式(十三)结构型:代理模式详解
  • spring gateway 配置http和websocket路由转发规则
  • NodeJs接入腾讯云存储COS
  • Ubuntu Linux 如何配置虚拟内存 —— 一步一步配置 Ubuntu Server 的 NodeJS 服务器详细实录8
  • USB设备调试
  • 全面理解JVM虚拟机
  • RK3568 Linux驱动学习——U-Boot使用
  • 六、搭建springCloudAlibaba2021.1版本分布式微服务-admin监控中心
  • Linux 基础命令大全
  • 内存泄漏问题排查
  • Context Engineering Notes
  • 【Golang】Go语言运算符
  • 迷宫生成与路径搜索(A算法可视化)
  • Triton IR
  • Libevent(4)之使用教程(3)配置
  • 如何使用ozone调试elf文件?
  • Dify 本地化部署深度解析与实战指南
  • LangChain实现RAG
  • 力扣 hot100 Day57
  • 第四科学范式(数据密集型科学):科学发现的新范式
  • Qt C++动态库SDK在Visual Studio 2022使用(C++/C#版本)
  • IIS发布.NET9 API 常见报错汇总
  • Java面试实战:从基础到架构的全方位技术交锋
  • add新增管理员功能、BaseController类的简介--------示例OJ
  • PDF转图片实用指南:如何批量高效转换?
  • AI入门学习-模型评估示例讲解
  • Deja Vu: 利用上下文稀疏性提升大语言模型推理效率
  • 【java】 IntelliJ IDEA高效编程设置指南