什么是享元模式?JavaScript 实现一个享元模式的示例
·
什么是享元模式?
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享多个对象中相同的部分(内部状态),来减少内存占用和对象数量,从而提高系统性能。
其核心思想是:将对象的状态分为内部状态(可共享、不随环境变化的部分)和外部状态(不可共享、随环境变化的部分)。通过共享内部状态,复用已创建的对象,仅在必要时通过外部状态区分不同场景。
享元模式的核心角色
- 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保相同内部状态的对象只被创建一次(复用已有对象)。
- 抽象享元(Flyweight):定义享元对象的接口,声明接收外部状态并执行操作的方法。
- 具体享元(ConcreteFlyweight):实现抽象享元接口,存储内部状态(可共享),通过外部状态处理具体逻辑。
- 非享元(UnsharedConcreteFlyweight):部分场景中,无需共享的对象可作为非享元存在(可选)。
JavaScript 实现享元模式示例:文字渲染系统
以下以 “网页文字渲染” 为例,实现享元模式。场景是:网页中存在大量相同样式(字体、大小、颜色)的文字,这些文字的样式(内部状态)可共享,而位置(x、y 坐标,外部状态)不同。通过享元模式复用样式对象,减少内存消耗。
代码实现
// -------------- 1. 抽象享元(Flyweight):文字样式接口 --------------
class TextStyle {
// 渲染文字(接收外部状态:位置)
render(content, x, y) {
throw new Error("子类必须实现 render 方法");
}
}
// -------------- 2. 具体享元(ConcreteFlyweight):具体文字样式 --------------
class ConcreteTextStyle extends TextStyle {
/**
* 构造函数:存储内部状态(可共享的样式)
* @param {string} font - 字体
* @param {number} size - 字号
* @param {string} color - 颜色
*/
constructor(font, size, color) {
super();
this.font = font; // 内部状态(共享)
this.size = size; // 内部状态(共享)
this.color = color; // 内部状态(共享)
// 记录创建的享元对象(用于演示复用)
this.id = `style_${Date.now().toString().slice(-4)}`;
console.log(`创建新样式对象:${this.id}(${font}, ${size}px, ${color})`);
}
/**
* 渲染文字(结合外部状态:位置)
* @param {string} content - 文字内容
* @param {number} x - x坐标(外部状态)
* @param {number} y - y坐标(外部状态)
*/
render(content, x, y) {
console.log(
`[${this.id}] 渲染文字:"${content}",样式:${this.font} ${this.size}px ${this.color},位置:(${x}, ${y})`
);
}
}
// -------------- 3. 享元工厂(Flyweight Factory):管理样式对象 --------------
class TextStyleFactory {
constructor() {
this.styles = {}; // 缓存享元对象(键:样式唯一标识,值:样式对象)
}
/**
* 获取样式对象(复用已有对象,不存在则创建)
* @param {string} font - 字体
* @param {number} size - 字号
* @param {string} color - 颜色
* @returns {ConcreteTextStyle} 样式享元对象
*/
getStyle(font, size, color) {
// 生成唯一键(基于内部状态)
const key = `${font}_${size}_${color}`;
// 若已存在则复用,否则创建新对象
if (!this.styles[key]) {
this.styles[key] = new ConcreteTextStyle(font, size, color);
}
return this.styles[key];
}
// 查看缓存的享元对象数量(用于演示)
getStyleCount() {
return Object.keys(this.styles).length;
}
}
// -------------- 4. 客户端使用 --------------
// 创建享元工厂
const styleFactory = new TextStyleFactory();
// 渲染多个文字(存在重复样式)
function renderTexts() {
// 场景1:3个"标题"(相同样式:Arial, 24px, 黑色)
const titleStyle = styleFactory.getStyle("Arial", 24, "black");
titleStyle.render("标题1", 10, 20);
titleStyle.render("标题2", 10, 60);
titleStyle.render("标题3", 10, 100);
// 场景2:2个"正文"(相同样式:SimSun, 16px, 灰色)
const bodyStyle1 = styleFactory.getStyle("SimSun", 16, "gray");
bodyStyle1.render("正文A", 50, 200);
bodyStyle1.render("正文B", 50, 230);
// 场景3:2个"注释"(相同样式:SimSun, 12px, 灰色)
const noteStyle = styleFactory.getStyle("SimSun", 12, "gray");
noteStyle.render("注释1", 50, 300);
noteStyle.render("注释2", 50, 320);
// 场景4:1个"强调文本"(新样式:Arial, 16px, 红色)
const highlightStyle = styleFactory.getStyle("Arial", 16, "red");
highlightStyle.render("重点内容", 50, 400);
}
// 执行渲染
renderTexts();
// 输出结果说明:
// 1. 仅创建4个样式对象(标题、正文、注释、强调文本),尽管渲染了8个文字。
// 2. 相同样式的文字复用同一个享元对象(如3个标题共用 titleStyle)。
console.log("\n缓存的样式对象数量:", styleFactory.getStyleCount()); // 输出:4
代码说明
-
抽象享元(TextStyle):定义
render方法,声明文字渲染的接口,需接收外部状态(x、y坐标)。 -
具体享元(ConcreteTextStyle):
- 存储内部状态:
font(字体)、size(字号)、color(颜色),这些属性在相同样式的文字中可共享。 - 实现
render方法:结合外部状态(x、y)渲染文字,展示样式和位置信息。
- 存储内部状态:
-
享元工厂(TextStyleFactory):
- 核心逻辑是
getStyle方法:通过内部状态(font、size、color)生成唯一键,若该键对应的样式已存在则复用,否则创建新对象并缓存。 - 确保相同样式的文字只创建一个享元对象,减少内存占用(示例中 8 个文字仅创建 4 个样式对象)。
- 核心逻辑是
-
内部状态与外部状态:
- 内部状态:
font、size、color(可共享,不随位置变化)。 - 外部状态:
x、y(不可共享,每个文字位置不同,通过render方法传入)。
- 内部状态:
享元模式的优势
- 减少对象数量:通过共享内部状态,大幅减少创建的对象数量(如示例中从 8 个减少到 4 个),降低内存消耗。
- 提高性能:减少对象创建和垃圾回收的开销,尤其适合大量相似对象的场景(如游戏中的粒子、网页中的文字 / 图标)。
- 分离可变与不可变:将不变的内部状态与可变的外部状态分离,使系统更清晰、易于维护。
常见应用场景
- 大量相似对象场景:如游戏中的士兵、子弹、粒子效果(共享模型、材质等内部状态,位置、方向等为外部状态)。
- 资源复用:如网页中的图标(相同图标复用同一个图片对象,位置为外部状态)、字体缓存。
- 池化技术:如数据库连接池、线程池(共享连接 / 线程对象,请求参数为外部状态)。
注意事项
- 状态划分:需明确区分内部状态(可共享)和外部状态(不可共享),否则可能因状态耦合导致共享失败。
- 工厂复杂度:享元工厂需管理缓存和键生成,可能增加代码复杂度,需权衡是否值得优化(仅在对象数量极多且相似时使用)。
享元模式的核心是 “共享相似对象,分离内外状态”,通过复用减少资源消耗,是性能优化的重要模式,尤其适合处理大量重复或相似对象的场景。
更多推荐

所有评论(0)