文章目录
- [享元模式(Flyweight Pattern)](#享元模式(Flyweight Pattern))
- 核心原理
- [Java 实践示例](#Java 实践示例)
- 享元模式的关键概念
- 享元模式的特点
- 享元模式的应用场景
享元模式(Flyweight Pattern)
享元模式是 23 种设计模式中的一种结构型模式,其核心思想是通过共享技术复用大量细粒度的相似对象,减少对象创建的数量,从而降低内存占用和提高系统性能。这种模式将对象的状态分为可共享的 "内在状态" 和不可共享的 "外在状态",通过共享内在状态来实现对象复用。
核心原理
- 抽象享元(Flyweight):
定义享元对象的接口,声明对外在状态的操作方法
内在状态作为对象的成员变量存储,外在状态通过方法参数传入 - 具体享元(ConcreteFlyweight):
实现抽象享元接口,存储可共享的内在状态
对外在状态进行处理,但不存储外在状态 - 享元工厂(FlyweightFactory):
负责创建和管理享元对象,确保合理共享
维护一个享元池(如哈希表),当请求享元时,优先从池中获取 - 客户端(Client):
负责维护享元对象的外在状态
通过享元工厂获取享元对象,并为其设置外在状态
享元模式的核心是 "分离内在状态与外在状态,共享内在状态",通过减少对象数量来优化系统资源消耗。
Java 实践示例
以 "文字处理系统" 为例实现享元模式:
文档中存在大量重复字符(如字母、数字),字符的字形(字体、大小)是可共享的内在状态
字符在文档中的位置(x,y 坐标)是不可共享的外在状态
通过享元模式共享相同字符对象,只存储一次字形信息,减少内存占用
java
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
public class FlyweightPattern {
public static void main(String[] args) {
// 创建享元工厂
CharacterFactory factory = new CharacterFactory();
// 获取并使用享元对象
CharacterFlyweight c1 = factory.getCharacter('A');
c1.display(10, 20); // 位置(10,20)
CharacterFlyweight c2 = factory.getCharacter('B');
c2.display(30, 40); // 位置(30,40)
CharacterFlyweight c3 = factory.getCharacter('A');
c3.display(50, 60); // 位置(50,60)
// 验证是否共享了相同对象
System.out.println("\n字符'A'的两个实例是否为同一对象:" + (c1 == c3));
System.out.println("工厂中缓存的享元数量:" + factory.getFlyweightCount());
//创建新的字符享元:A
//显示字符 'A' (字体:Arial, 字号:12) 在位置 (10,20)
//创建新的字符享元:B
//显示字符 'B' (字体:Arial, 字号:12) 在位置 (30,40)
//复用已有的字符享元:A
//显示字符 'A' (字体:Arial, 字号:12) 在位置 (50,60)
//
//字符'A'的两个实例是否为同一对象:true
//工厂中缓存的享元数量:2
}
// 抽象享元:字符接口
public interface CharacterFlyweight {
// 显示字符,x和y是外在状态(位置)
void display(int x, int y);
}
// 具体享元:字符实现类
public static class ConcreteCharacter implements CharacterFlyweight {
// 内在状态:字符值(可共享)
private char c;
// 内在状态:字体(可共享)
private String font;
// 内在状态:字号(可共享)
private int size;
// 构造函数初始化内在状态
public ConcreteCharacter(char c) {
this.c = c;
// 模拟默认字体和字号(实际中可通过参数传入)
this.font = "Arial";
this.size = 12;
}
@Override
public void display(int x, int y) {
// 外在状态(x,y)由客户端传入,不存储在享元中
System.out.printf("显示字符 '%c' (字体:%s, 字号:%d) 在位置 (%d,%d)\n",
c, font, size, x, y);
}
}
public static class CharacterFactory {
// 享元池:缓存已创建的享元对象
private Map<Character, CharacterFlyweight> flyweightPool = new HashMap<>();
// 获取享元对象:存在则从池获取,不存在则创建并缓存
public CharacterFlyweight getCharacter(char c) {
// 检查池中是否存在
if (!flyweightPool.containsKey(c)) {
// 不存在则创建新的享元对象
flyweightPool.put(c, new ConcreteCharacter(c));
System.out.println("创建新的字符享元:" + c);
} else {
System.out.println("复用已有的字符享元:" + c);
}
return flyweightPool.get(c);
}
// 获取缓存的享元数量
public int getFlyweightCount() {
return flyweightPool.size();
}
}
}
享元模式的关键概念
内在状态(Intrinsic State):
存储在享元对象内部,可被多个对象共享的状态
不随环境变化,例如示例中的字符值、字体、字号
在享元对象创建时初始化,之后不再改变
外在状态(Extrinsic State):
随环境变化,不能被共享的状态
由客户端维护,在使用享元时通过方法参数传入
例如示例中的字符位置(x,y 坐标)
享元模式的特点
优点:
减少对象数量:通过共享相似对象,显著降低内存占用
提高性能:减少对象创建和垃圾回收的开销
分离内在与外在状态:使享元对象更专注于核心功能
缺点:
增加系统复杂度:需要分离状态并管理享元池
外在状态处理成本:可能需要客户端维护复杂的外在状态
适用条件:
系统中存在大量相似对象,且创建成本高
对象的大部分状态可以外部化
存在访问这些对象的集中点(便于使用工厂管理)
享元模式的应用场景
字符串常量池:
Java 中的String常量池是享元模式的经典实现
相同字符串字面量会被共享,如"abc" == "abc"返回true
数据库连接池:
连接池缓存数据库连接对象,避免频繁创建和关闭连接
连接的用户名、密码等是内在状态,每次查询参数是外在状态
游戏开发:
大量相似游戏元素(如树木、士兵、粒子)共享模型数据
位置、朝向等是外在状态,模型、纹理等是内在状态
GUI 组件:
相同样式的按钮、标签等组件共享渲染信息
位置、大小等是外在状态,颜色、字体等是内在状态
缓存框架:
如 Redis、EhCache 等缓存框架,通过共享缓存对象提高访问速度
享元模式是优化系统资源的重要手段,尤其适合处理大量细粒度相似对象的场景。其核心在于合理划分内在状态与外在状态,通过享元工厂实现对象的高效复用,从而在内存占用和性能之间取得平衡。