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

Java设计模式-享元模式

Java设计模式-享元模式

模式概述

享元模式简介

核心思想:通过共享技术高效地支持大量细粒度对象的复用,将对象的公共状态(内部状态)与私有状态(外部状态)分离,仅创建一次公共状态的对象,私有状态在使用时动态传入,从而减少内存占用并提升性能。

模式类型:结构型设计模式(关注对象的组成与复用)。

作用

  • 减少内存消耗:通过共享重复对象,避免大量相似对象的重复创建。
  • 提升系统性能:降低对象创建/销毁的开销,减少垃圾回收压力。
  • 简化对象管理:通过工厂类集中管理共享对象,统一控制对象的生命周期。

典型应用场景

  • 文本编辑器/IDE中的字符对象(如Word中大量重复的字母“a”,仅存储一份字符编码,位置、颜色等动态传入)。
  • 游戏开发中的粒子系统(如大量相同属性的子弹、火花,共享基础属性,位置、速度等动态计算)。
  • 数据库连接池(复用已创建的数据库连接,避免频繁创建/关闭连接的开销)。
  • 缓存系统(如Guava Cache,通过共享缓存对象减少重复计算)。

我认为:享元模式是“对象复用”的经典实践,用空间换时间(或用共享换内存),让大量相似对象“共用一张脸,各自有不同的姿态”。

课程目标

  • 理解享元模式的核心思想和经典应用场景
  • 识别应用场景,使用享元模式解决功能要求
  • 了解享元模式的优缺点

核心组件

角色-职责表

角色职责示例类名
抽象享元角色(Flyweight)定义享元对象的公共接口,声明操作外部状态的方法Character
具体享元角色(ConcreteFlyweight)实现抽象接口,存储内部状态(不可变),依赖外部状态完成操作ConcreteCharacter
非共享享元角色(UnsharedFlyweight)不可共享的享元对象(特殊场景使用,如需要个性化配置的对象)SpecialCharacter
享元工厂(FlyweightFactory)管理享元对象的创建与共享,确保相同内部状态的对象仅创建一次CharacterFactory

类图

下面是一个简化的类图表示,展示了享元模式中的主要角色及其交互方式:

实现
创建/管理
«interface»
FlyweightCharacter
+display(int row, int col)
ConcreteCharacter
-String code // 内部状态(不可变)
+ConcreteCharacter(String)
+display(int row, int col)
CharacterFactory
-Map pool // 缓存池(code→字符对象)
+getInstance() : CharacterFactory
+getCharacter(String code) : FlyweightCharacter
+poolSize() : int

传统实现 VS 享元模式

案例需求

案例背景:实现一个文本编辑器的基础字符显示功能,要求支持大量字符(如字母、数字)的显示,每个字符需记录其编码(如’A’、‘1’)和位置(行号、列号)。

传统实现(痛点版)

代码实现

// 传统实现:每个字符独立创建对象
class Character {private String code;  // 字符编码(如'A')private int row;      // 行号(外部状态)private int col;      // 列号(外部状态)public Character(String code, int row, int col) {this.code = code;this.row = row;this.col = col;}public void display() {System.out.printf("字符:%s,位置:(%d,%d)%n", code, row, col);}
}// 使用示例:创建10000个'A'字符
public class Client {public static void main(String[] args) {List<Character> characters = new ArrayList<>();// 创建10000个'A',每个都是独立对象(内存浪费)for (int i = 0; i < 10000; i++) {characters.add(new Character("A", i / 100, i % 100)); }// 显示所有字符characters.forEach(Character::display);}
}

痛点总结

  • 内存浪费严重:10000个’A’字符被创建为10000个独立对象,但它们的code字段完全相同(仅rowcol不同)。
  • 性能低下:大量对象创建/销毁增加GC压力,尤其在高频操作(如文本输入)时可能导致卡顿。
  • 管理复杂:无法统一控制字符对象的生命周期(如批量回收或修改共享属性)。

享元模式 实现(优雅版)

代码实现

// 1. 抽象享元角色:定义字符的公共接口
interface FlyweightCharacter {void display(int row, int col);  // 显示方法(接收外部状态:行、列)
}// 2. 具体享元角色:实现字符的共享逻辑(内部状态:code)
class ConcreteCharacter implements FlyweightCharacter {private final String code;  // 内部状态(不可变,线程安全)public ConcreteCharacter(String code) {this.code = code;}@Overridepublic void display(int row, int col) {System.out.printf("字符:%s,位置:(%d,%d)%n", code, row, col);}
}// 3. 享元工厂:管理字符对象的共享(单例模式)
class CharacterFactory {private static final CharacterFactory INSTANCE = new CharacterFactory();private final Map<String, FlyweightCharacter> pool = new HashMap<>();  // 缓存池(code→字符对象)private CharacterFactory() {}  // 私有构造public static CharacterFactory getInstance() {return INSTANCE;}// 获取或创建字符对象(关键:仅当code不存在时新建)public FlyweightCharacter getCharacter(String code) {if (!pool.containsKey(code)) {pool.put(code, new ConcreteCharacter(code));  // 仅创建一次}return pool.get(code);}// 统计缓存大小(用于验证共享效果)public int poolSize() {return pool.size();}
}// 4. 使用示例:通过工厂获取共享字符对象
public class Client {public static void main(String[] args) {// 创建10000个'A'字符(实际仅创建1个对象)List<FlyweightCharacter> characters = new ArrayList<>();FlyweightCharacter aChar = CharacterFactory.getInstance().getCharacter("A");for (int i = 0; i < 10000; i++) {characters.add(aChar);  // 直接复用共享对象}// 显示所有字符(外部状态:行、列动态传入)characters.forEach(charObj -> charObj.display(i / 100, i % 100)  // 注意:此处i需替换为实际循环变量);// 验证共享效果:字符池仅1个对象System.out.println("字符池大小:" + CharacterFactory.getInstance().poolSize());  // 输出:1}
}

优势

  • 内存占用骤降:10000个’A’字符仅创建1个ConcreteCharacter对象,内存节省99.99%。
  • 性能提升:避免了大量对象的创建/销毁开销,GC压力显著降低。
  • 易于管理:通过工厂类统一控制字符对象的生命周期(如后续可扩展支持字符样式的批量修改)。

局限

  • 状态分离复杂度:需明确区分内部状态(不可变)和外部状态(动态传入),设计不当可能导致逻辑混乱。
  • 线程安全问题:若工厂类非线程安全(如使用普通HashMap),多线程并发获取对象时可能导致数据不一致(可通过ConcurrentHashMap解决)。
  • 过度共享风险:若对象的外部状态过多或变化频繁,可能导致方法参数膨胀,降低代码可读性。

模式变体

  • 线程安全享元工厂:使用ConcurrentHashMap作为缓存池,或在工厂方法中添加同步锁,确保多线程环境下对象共享的安全性。
  • 带缓存的享元工厂:结合LRU(最近最少使用)淘汰策略,限制缓存池大小,避免内存溢出(适用于对象数量极多的场景)。
  • 复合享元模式:将多个简单享元对象组合成复合享元(如“单词”由多个字符组成),支持更复杂的对象复用(如文本编辑器中的单词级共享)。
  • 临时享元:支持设置享元对象的过期时间(如缓存中的临时字符样式),自动回收失效对象(结合定时任务或弱引用实现)。

最佳实践

建议理由
明确区分内部状态与外部状态内部状态(如字符编码)需不可变且与上下文无关,外部状态(如位置)需动态传入,否则无法共享。
工厂类使用单例模式确保全局仅一个缓存池实例,避免重复创建导致共享失效。
限制外部状态的数量外部状态过多会导致方法参数复杂,降低代码可维护性(建议不超过3个)。
对性能敏感场景预加载提前将高频使用的享元对象(如常用字符)加载到工厂池中,避免运行时动态创建。
添加监控与日志记录缓存池的命中次数、内存占用等指标,便于优化共享策略(如调整缓存大小)。

一句话总结

享元模式通过分离对象的内部状态与外部状态,利用共享机制大幅减少内存占用,是处理大量相似对象场景的高效解决方案。

如果关注Java设计模式内容,可以查阅作者的其他Java设计模式系列文章。😊

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

相关文章:

  • FreeRTOS学习笔记(三)--汇编指令
  • C ++代码学习笔记(一)
  • Layui 中的 iframe 详解与最佳实践
  • Linux笔记7——shell编程基础-1
  • SMT车间如何通过防静电监控系统提升产品质量
  • 206.反转链表
  • 【时时三省】vectorCAST 便捷使用技巧
  • 利用 Python 爬虫获取淘宝商品评论实战指南
  • 并发编程原理与实战(二十五)手写简易线程池实战,剖析线程状态转换过程
  • 【LINUX网络】UDP协议基础原理
  • Netty AdaptiveRecvByteBufAllocator原理详解
  • 分布式消息队列技术(原理相关):Kafka
  • 机器学习3
  • 《WINDOWS 环境下32位汇编语言程序设计》第6章 定时器和Windows时间
  • Git 版本控制核心流程与协作指南(从入门到冲突解决)
  • windows下jdk环境切换为jdk17后,临时需要jdk1.8的处理
  • 机器学习笔试题
  • 如何利用淘宝API接口实现自动下单?实战案例讲解
  • 【无标题】GAP: 用文本指导对任何点云进行高斯化(ICCV 2025)
  • 香港云服务器被暴力破解的危害有多大?
  • 使用docker manifest制作本地多架构镜像
  • Java与Vue深度融合,构建资产设备全周期管理系统,集成移动端实时操作与后台智能管理,功能完备且附完整源码,助力企业实现资产数字化高效运维
  • ChatBI如何重塑企业数据分析?2025年智能BI行业趋势解读
  • 使用 TensorBoardX 实现 PyTorch 神经网络可视化:从入门到进阶
  • Chrome 插件开发实战:从入门到进阶
  • Python 面向对象编程入门:从思想到属性操作
  • PyTorch 环境配置
  • Telnet、ftp详解
  • 教育场景下禁用html5播放器拖动进度条的例子
  • python 项目编号 2025821 有关于中英文数据的收集、处理