设计模式——享元设计模式(结构型)

article/2025/7/29 14:39:49

摘要

享元设计模式是一种结构型设计模式,旨在通过共享对象减少内存占用和提升性能。其核心思想是将对象状态分为内部状态(可共享)和外部状态(不可共享),并通过享元工厂管理共享对象池。享元模式包含抽象享元类、具体享元类、非共享具体享元类和享元工厂类。它适用于处理大量相似对象的场景,如文档编辑器中的字符对象。文章还提供了享元模式的实现方式、适合与不适合的使用场景、实战示例以及与其他设计模式的比较。

1. 享元设计模式定义

享元设计模式(Flyweight Pattern) 是一种结构型设计模式,用于减少对象的数量,以节省内存和提高性能。享元模式通过共享内存中已经存在的对象,避免重复创建相同内容的对象,适用于大量相似对象的场景。

1.1.1. 核心思想

把对象状态划分为:

  • 内部状态(可共享,存储在享元对象中)
  • 外部状态(不可共享,由客户端维护)

享元工厂(FlyweightFactory): 负责管理共享对象的池,复用已有实例。

1.1.2. 举例说明

假设一个文档编辑器中有 10 万个字符,但字符集就 128 个(ASCII),如果每个字符都是独立对象,将浪费大量内存。此时可以:

  • 把字符内容作为内部状态(可共享)
  • 把字体、大小、颜色作为外部状态(由外部控制)
  • 同一个字符内容只创建一次对象,由享元工厂复用

2. 享元设计模式结构

享元模式包含如下角色:

  • Flyweight: 抽象享元类
  • ConcreteFlyweight: 具体享元类
  • UnsharedConcreteFlyweight: 非共享具体享元类
  • FlyweightFactory: 享元工厂类

2.1. 享元设计模式类图

2.2. 享元设计模式时序图

3. 享元设计模式实现方式

享元设计模式的实现方式主要围绕 对象共享池内部状态与外部状态的分离。它通过一个享元工厂(FlyweightFactory)来管理共享对象,避免重复创建,从而节省内存。

3.1. 1️⃣ 定义抽象享元接口(Flyweight)

public interface Flyweight {void operation(String externalState); // 外部状态由调用方传入
}

3.2. 2️⃣ 创建具体享元类(ConcreteFlyweight)

public class ConcreteFlyweight implements Flyweight {private final String intrinsicState; // 内部状态,能共享public ConcreteFlyweight(String intrinsicState) {this.intrinsicState = intrinsicState;}@Overridepublic void operation(String externalState) {System.out.println("共享[" + intrinsicState + "],非共享[" + externalState + "]");}
}

3.3. 3️⃣ 创建享元工厂类(FlyweightFactory)

public class FlyweightFactory {private final Map<String, Flyweight> pool = new HashMap<>();public Flyweight getFlyweight(String key) {if (!pool.containsKey(key)) {pool.put(key, new ConcreteFlyweight(key));}return pool.get(key);}public int getPoolSize() {return pool.size();}
}

3.4. 4️⃣ 客户端调用(Client)

public class Client {public static void main(String[] args) {FlyweightFactory factory = new FlyweightFactory();Flyweight a1 = factory.getFlyweight("A");Flyweight a2 = factory.getFlyweight("A"); // 重复,不创建新对象a1.operation("外部状态1");a2.operation("外部状态2");System.out.println("共享对象数量:" + factory.getPoolSize()); // 输出:1}
}

3.5. ✅ 享元设计模式总结

要素

说明

内部状态

可以共享,存储在享元对象中,如字符、类型

外部状态

每次调用时由客户端传入,不在享元内部存储

工厂类

管理共享对象的创建与复用

缓存池

使用 HashMap 或 ConcurrentHashMap 存储共享对象

线程安全注意点

在并发环境下需保证工厂创建逻辑线程安全(如加锁或使用 ConcurrentMap)

4. 享元设计模式适合场景

4.1. ✅ 适合使用享元设计模式的场景

场景

说明

大量重复对象需创建,且状态大部分相同

比如:文字编辑器中的字符对象、地图中的草地格子、游戏中的粒子等,能显著减少内存开销。

对象创建成本高,希望通过复用来减少系统负担

比如:金融系统中共享的“黑名单规则”、“风控维度元数据”等。

对象状态可拆分为可共享的内部状态和不可共享的外部状态

共享部分放入享元,变动部分交给外部传入,从而实现高复用。

系统中存在大量细粒度对象,结构相似、功能一致

如图形系统中的形状节点、文档编辑器中的字体、格式等。

缓存池或对象池机制的实现场景

比如:数据库连接池、线程池、元数据池、图标池、缓存字典等。

4.2. ❌ 不适合使用享元模式的场景

场景

原因

对象状态频繁变化,不可拆分共享/不共享状态

对象状态不能被提取为外部状态时,就无法有效共享,甚至会导致共享污染。

对象之间差异太大,无法复用或没有可共享部分

如果每个对象都是完全不同的个体,享元模式无法带来价值。

系统对对象独立性要求高,不允许共享

比如线程不安全或敏感业务逻辑要求每个对象独立维护生命周期。

对象数量本身不多,内存开销可以接受

引入享元结构反而增加了系统复杂度、调试难度,不值得。

共享对象内部包含资源引用,如 Socket、File 等

资源不允许多个业务共享,使用享元会造成资源冲突或数据错乱。

4.3. 🧠 享元模式的场景总结

项目

适合使用享元模式

不适合使用享元模式

对象数量

✅ 大量重复

❌ 数量少

状态结构

✅ 可拆分内外状态

❌ 状态复杂或强耦合

系统压力

✅ 内存敏感,需优化

❌ 性能足够,优化收益低

可复用性

✅ 可复用部分明显

❌ 无明显共享逻辑

系统复杂度

✅ 有控制成本价值

❌ 小项目/临时代码

5. 享元设计模式实战示例

下面是一个 享元设计模式在金融风控系统中的 Spring 实战示例,场景为:风控规则元数据共享池,用于缓存和复用规则的静态定义,减少重复加载和内存占用。在风控系统中,不同的策略规则经常引用相同的“规则定义”(如规则编号、描述、字段映射等)。这些定义是 不可变 的、重复使用 的,适合使用享元模式来缓存复用。

5.1. ✅ 享元接口:RuleDefinition

public interface RuleDefinition {void evaluate(String param);  // 示例行为
}

5.2. ✅ 具体享元类:ConcreteRuleDefinition

public class ConcreteRuleDefinition implements RuleDefinition {private final String ruleCode;private final String description;public ConcreteRuleDefinition(String ruleCode, String description) {this.ruleCode = ruleCode;this.description = description;}@Overridepublic void evaluate(String param) {System.out.println("执行规则 [" + ruleCode + "] - " + description + ",参数:" + param);}public String getRuleCode() {return ruleCode;}public String getDescription() {return description;}
}

5.3. ✅ 享元工厂:RuleDefinitionFactory(由 Spring 管理)

@Component
public class RuleDefinitionFactory {private final Map<String, RuleDefinition> pool = new ConcurrentHashMap<>();/*** 获取共享的规则定义*/public RuleDefinition getRule(String ruleCode) {return pool.computeIfAbsent(ruleCode, this::loadRuleDefinition);}/*** 模拟从数据库或配置中加载规则元数据*/private RuleDefinition loadRuleDefinition(String ruleCode) {// 实际情况应从数据库或配置中心加载System.out.println("加载规则定义:" + ruleCode);return new ConcreteRuleDefinition(ruleCode, "规则描述_" + ruleCode);}public int getPoolSize() {return pool.size();}
}

5.4. ✅ 客户端服务:RiskEngineService(注入使用)

@Service
public class RiskEngineService {@Autowiredprivate RuleDefinitionFactory ruleDefinitionFactory;public void processRisk(String ruleCode, String inputParam) {RuleDefinition rule = ruleDefinitionFactory.getRule(ruleCode);rule.evaluate(inputParam);}
}

5.5. ✅ 启动类或控制器测试(模拟调用)

@RestController
public class RiskController {@Autowiredprivate RiskEngineService riskEngineService;@GetMapping("/risk/test")public String test() {riskEngineService.processRisk("R001", "用户A数据");riskEngineService.processRisk("R001", "用户B数据");riskEngineService.processRisk("R002", "用户C数据");return "风控规则执行完毕";}
}

5.6. ✅ 运行结果示例

加载规则定义:R001
执行规则 [R001] - 规则描述_R001,参数:用户A数据
执行规则 [R001] - 规则描述_R001,参数:用户B数据
加载规则定义:R002
执行规则 [R002] - 规则描述_R002,参数:用户C数据

可见:R001 只加载一次,后续复用;实现了享元模式在 Spring 项目下的实战落地。

要素

说明

享元类

ConcreteRuleDefinition

享元工厂

RuleDefinitionFactory

共享池

ConcurrentHashMap缓存规则

注解注入

使用 @Autowired,不采用构造函数注入

应用场景

风控规则元数据复用、规则模板复用、评分模型共享

6. 享元设计模式思考

6.1. 享元设计模式与原型设计模式?

享元设计模式(Flyweight)和原型设计模式(Prototype)都是创建相关的设计模式,但它们解决的问题、使用方式和结构完全不同。下面是它们的详细对比:

6.1.1. 🆚 Flyweight vs. Prototype

维度

享元模式(Flyweight)

原型模式(Prototype)

💡 设计模式类型

结构型模式

创建型模式

🎯 目的

通过共享对象来减少内存占用和对象数量

通过复制已有对象来创建新对象,避免 new 开销

📦 关注点

对象复用与共享,分离内部状态与外部状态

快速创建新对象(特别是复杂结构)

🧠 实现机制

享元工厂维护共享对象池,通过传入外部状态来复用对象

使用 clone()

方法或拷贝构造函数复制现有对象

📂 状态管理

内部状态共享,外部状态由使用方维护

完整复制所有状态(深拷贝/浅拷贝)

📈 适合场景

- 大量重复对象,如文字编辑器中的字符对象
- 数据缓存池
- 游戏中的粒子/地图图块共享

- 克隆原型对象
- 对象构造成本高(如树状结构)

🧩 示例

String Pool、Integer.valueOf、数据库连接池

原型注册器、工作流模板复制、前端组件克隆等

⚠️ 使用注意点

- 内外部状态划分要明确
- 不适合状态频繁变化对象

- 深拷贝需注意引用类型对象

6.1.2. ✅ 简单总结

  • 享元模式 = 节省内存、共享对象:适用于大量对象重复的场景。
  • 原型模式 = 快速复制、提升性能:适用于快速创建复杂对象的场景。

6.1.3. 📌 举个类比:

类比

描述

享元

比如一个图书馆的“图书”是共享的,用户借用的是引用,图书本身不复制。

原型

比如一个表格模板,每次创建新文档都是复制模板,然后修改。

博文参考

  • 5. 享元模式 — Graphic Design Patterns
  • 享元设计模式

http://www.hkcw.cn/article/pBIoVbiFHN.shtml

相关文章

Qt OpenGL编程常用类

Qt提供了丰富的类来支持OpenGL编程&#xff0c;以下是常用的Qt OpenGL相关类&#xff1a; 一、QOpenGLWidget 功能&#xff1a;用于在 Qt 应用程序中嵌入 OpenGL 渲染的窗口部件。替代了旧版的QGLWidget。提供了OpenGL上下文和渲染表面。 继承关系&#xff1a;QWidget → QOp…

【JMeter】性能测试知识和工具

目录 何为系统性能 何为性能测试 性能测试分类 性能测试指标 性能测试流程 性能测试工具&#xff1a;JMeter&#xff08;主测web应用&#xff09; jmeter文件目录 启动方式 基本元件&#xff1a;元件内有很多组件 jmeter参数化 jmeter关联 自动录制脚本 直连数据库…

[Linux] nginx源码编译安装

初次学习&#xff0c;如有错误欢迎指正 目录 环境包部署 创建程序用户 软件包压缩 配置 编译 安装 建立快捷启动 启动nginx&#xff1f; 防火墙管理 查看规则 清空规则 关闭服务 开启服务 查看状态 开机自启 开机禁用 查看开机启动状态 nginx&#xff0c;启…

Spring AI Image Model、TTS,RAG

文章目录 Spring AI Alibaba聊天模型图像模型Image Model API接口及相关类实现生成图像 语音模型Text-to-Speech API概述实现文本转语音 实现RAG向量化RAGRAG工作流程概述实现基本 RAG 流程 Spring AI Alibaba Spring AI Alibaba实现了与阿里云通义模型的完整适配&#xff0c;…

多地机关食堂端午假期向社会开放 特色套餐迎客来

端午假期期间,全国多地政府机关食堂面向社会公众开放。5月31日中午,荣昌区政府机关食堂如约向游客开放,首日第一餐吸引了超过3000名游客前来体验。荣昌区特别推出了61元的“六一”家庭套餐,包含荣昌卤鹅、猪油泡粑、黄凉粉等特色菜品,还新增了粽子和儿童喜欢的薯条、鸡腿、…

韩国大选“5选1”投票将启 三强格局形成

6月3日,韩国将迎来新一届总统选举。最初有7名候选人登记参选,但截至6月2日,已有两名候选人宣布退出,形成了“5选1”的局面。目前李在明、金文洙和李俊锡基本形成三强格局。4名韩国前总统也各自进行着“路演”,通过各种方式表达对各自阵营候选人的支持。尹锡悦5月31日表态支…

美联邦调查局称科罗拉多州发生恐袭 燃烧瓶袭击游行人群

美国联邦调查局(FBI)局长卡什帕特尔在社交媒体上表示,6月1日科罗拉多州博尔德市发生了一起有针对性的恐怖袭击事件。FBI正在对此进行全面调查。FBI特工和当地执法人员已到达案发现场,并将在获得更多信息后分享最新情况。同日下午,科罗拉多州博尔德市的一个购物中心发生了袭…

第二轮谈判 乌公布代表团14人名单 防长继续带队

俄罗斯代表团已抵达土耳其伊斯坦布尔,准备参加即将举行的俄乌谈判。俄谈判代表团团长梅金斯基在抵达后表示,关于乌克兰谈判的所有评论将在6月2日公布,并会在当天详细说明俄罗斯在乌克兰问题上的立场。对于乌克兰对俄罗斯境内目标可能发起的攻击及其影响,俄方代表团成员、俄…

MQTT入门实战宝典:从零起步掌握物联网核心通信协议

MQTT入门实战宝典&#xff1a;从零起步掌握物联网核心通信协议 前言 物联网时代&#xff0c;万物互联已成为现实&#xff0c;而MQTT协议作为这个时代的"数据总线"&#xff0c;正默默支撑着从智能家居到工业物联的各类应用场景。本文将带你揭开MQTT的神秘面纱&#…

腾讯位置商业授权行政区划开发指南

概述 本服务提供中国标准行政区划数据查询功能&#xff0c;支持&#xff1a; 1 . 全国省、市、区/县、乡镇/街道 四级行政区划数据&#xff1b; 2 . 支持三级区划&#xff08;省/市 - 区/县&#xff09;轮廓数据&#xff1b; 3 . 支持区划查询、省市区列表、查询子级区划等功能…

GIS数据类型综合解析

GIS数据类型综合解析 目录 GIS数据类型综合解析1. 总体介绍2. GIS数据类型分类与对比2.1 主要数据类型对比表 3. 详细解析与扩展内容3.1 矢量数据&#xff08;Vector Data&#xff09;3.2 栅格数据&#xff08;Raster Data&#xff09;3.3 属性数据&#xff08;Attribute Data&…

Spring框架学习day5--AOP概念以及示例实现

AOP(面向切面编程) 1.概述 AOP为AspectOrientedProgramming 的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过 预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 AOP是OOP的延续&#xff0c;是软件开发中的一个热点&#xff0c;是java开发中的…

Python实现HPSO-TVAC优化算法优化支持向量机SVC分类模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在当今数据驱动的时代&#xff0c;支持向量机&#xff08;SVM&#xff09;作为一种经典的机器学习算法&#xff0c;…

警方回应10岁男孩儿童节前走失 仍在全力搜寻中

6月1日,山东省滕州市姜屯镇黄坡村一名10岁的小男孩赵某超走失,孩子家属通过网络社交媒体求助。事后家属查看家门口的监控发现,孩子是5月31日下午5时左右走失的。当时孩子消失在家门口的监控中后几分钟返回了一次,孩子的外公王先生在屋后的黄瓜地里插杆子,邻居还给了孩子一…

[SAP] 矩阵复制(Matrix Copy)

SAP中的复制粘贴功能被称为矩阵复制&#xff0c;通过点击对话框或屏幕&#xff0c;并执行下述命令&#xff0c;使用矩阵复制就可以复制多行文本 ① 按下Ctrl-Y&#xff0c;从左上到右下拖拉鼠标来选择文本 ② 文本高亮显示后&#xff0c;按下Ctrl-C ③ 移到新的位置插入文本…

2024年数维杯国际大学生数学建模挑战赛B题空间变量协同估计方法研究解题全过程论文及程序

2024年数维杯国际大学生数学建模挑战赛 B题 空间变量协同估计方法研究 原题再现&#xff1a; 在数理统计学中&#xff0c;简单采样通常假设来自相同总体的采样点彼此独立。与数理统计相反&#xff0c;空间统计假设空间变量的采样点是相依的&#xff0c;并在其值中表现出某些趋…

SPA-RL:通过Stepwise Progress Attribution训练LLM智能体

SPA-RL&#xff1a;通过Stepwise Progress Attribution训练LLM智能体 在大语言模型&#xff08;LLM&#xff09;驱动智能体发展的浪潮中&#xff0c;强化学习&#xff08;RL&#xff09;面临着延迟奖励这一关键挑战。本文提出的SPA-RL框架&#xff0c;通过创新的分步进度归因机…

基于 Zynq 平台的 EtherCAT 主站的软硬件协同设计

摘要: 针对工业自动化对控制能力和强实时性的需求&#xff0c;提出了一种基于 FPGA 的改进型 EtherCAT 硬件主站方案 。 该方案利用 Zynq&#xff0d;7000 平台&#xff0c;在 PL 端实现 FPGA 协议栈&#xff0c;以保证核心功能的高效执 行 。 基于 AXI4 总线设计…

【IC】BSIM-CMG:用于高级电路设计的标准FinFET紧凑型模型

摘要 这项工作提出了新的紧凑型模型&#xff0c;这些模型捕捉了工业FinFET中呈现的高级物理效应。所提出的模型被引入到行业标准紧凑型模型BSIM-CMG中。核心模型被更新为新的统一FinFET模型&#xff0c;该模型计算具有复杂鳍片横截面的晶体管的电荷和电流。此外&#xff0c;来…

BFD工作原理(双向转发检测)

BFD的工作原理 BFD 会在两台网络设备之间建立会话&#xff0c;并通过周期性地交换 BFD 控制报文来检测路径的连通性。如果在检测时间内没有收到对方的报文&#xff0c;则视为链路故障&#xff0c;是依赖于路由协议来发现邻居的 故障检测 故障检测时间 协同接收间隔 * 检测倍数…