轻量级模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少内存使用和提高性能,适用于需要创建大量相似对象的场景。它将对象的内在状态(共享、不变的部分)与外在状态(变化、上下文相关的部分)分离,通过复用内在状态减少对象实例数量。
设计模式原理
轻量级模式的核心是通过工厂类管理共享的轻量级对象(Flyweight),将内在状态存储在共享对象中,外在状态由客户端提供,从而减少内存占用。例如,在文本编辑器中,相同的字符(如“A”)可能在文档中出现多次,但只需要一个共享对象存储其字体和样式,位置信息则由上下文动态提供。
结构
- 轻量级接口(Flyweight):定义操作方法,接收外在状态。
- 具体轻量级(ConcreteFlyweight):实现轻量级接口,存储内在状态。
- 轻量级工厂(FlyweightFactory):管理共享的轻量级对象,确保相同内在状态的对象只创建一次。
- 客户端(Client):维护外在状态,调用轻量级对象执行操作。
优点
- 内存优化:通过共享内在状态,大幅减少对象数量和内存使用。
- 性能提升:减少对象创建开销,适合大量相似对象场景。
- 可扩展:通过工厂模式,易于添加新共享对象。
- 职责分离:内在和外在状态分离,逻辑清晰。
缺点
- 复杂性增加:需要维护工厂和状态分离逻辑,代码复杂度可能上升。
- 外在状态管理:客户端需自行管理外在状态,增加负担。
- 线程安全:共享对象需考虑并发访问问题。
- 适用局限:仅适合大量相似对象的场景。
适用场景
- 大量相似对象:如文本编辑器中的字符、游戏中的精灵。
- 内存敏感系统:如移动端应用、嵌入式设备。
- 重复状态:对象有大量可共享的内在状态。
- 渲染系统:如GUI框架、图形编辑器。
在字符渲染系统中,轻量级模式通过共享字符的字体和样式,显著减少内存占用,同时通过位置信息灵活渲染。
TypeScript 实现示例
我们实现一个经典的文本编辑器字符渲染系统,通过轻量级模式共享字符的字体和样式,管理字符的坐标位置。假设文档中有大量重复的字符(如“A”或“B”),我们只为每种字符和样式组合创建一个对象,位置信息由客户端维护。代码使用TypeScript确保类型安全。
项目结构
text-editor/
├── src/
│ ├── flyweight.ts // 轻量级接口和具体轻量级
│ ├── factory.ts // 轻量级工厂
│ ├── editor.ts // 客户端(文本编辑器)
│ ├── main.ts // 执行示例
├── tsconfig.json
├── package.json
1. 安装依赖
npm init -y
npm install typescript @types/node
npx tsc --init
配置 tsconfig.json
:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"moduleResolution": "node"
}
}
2. 定义轻量级接口和具体轻量级 (flyweight.ts)
export interface CharacterFlyweight {
render(extrinsicState: { x: number; y: number }): void;
}
export class ConcreteCharacterFlyweight implements CharacterFlyweight {
constructor(private char: string, private font: string, private fontSize: number) {
console.log(`创建轻量级字符:字符="${char}", 字体="${font}", 字号=${fontSize}`);
}
render(extrinsicState: { x: number; y: number }): void {
console.log(`渲染字符 "${this.char}" 在位置 (${extrinsicState.x}, ${extrinsicState.y}), 字体="${this.font}", 字号=${this.fontSize}`);
}
}
说明:ConcreteCharacterFlyweight
存储内在状态(char
、font
、fontSize
),render
方法接收外在状态(x
、y
坐标)来渲染字符。
3. 实现轻量级工厂 (factory.ts)
import { CharacterFlyweight, ConcreteCharacterFlyweight } from './flyweight';
export class CharacterFlyweightFactory {
private flyweights: Map<string, CharacterFlyweight> = new Map();
getFlyweight(char: string, font: string, fontSize: number): CharacterFlyweight {
const key = `${char}_${font}_${fontSize}`;
if (!this.flyweights.has(key)) {
this.flyweights.set(key, new ConcreteCharacterFlyweight(char, font, fontSize));
}
return this.flyweights.get(key)!;
}
getFlyweightCount(): number {
return this.flyweights.size;
}
}
说明:工厂通过Map
缓存字符对象,确保相同字符和样式组合(如“A, Arial, 12”)只创建一次。getFlyweight
方法返回共享对象。
4. 实现客户端 (editor.ts)
import { CharacterFlyweight, CharacterFlyweightFactory } from './factory';
interface Character {
char: string;
font: string;
fontSize: number;
x: number;
y: number;
}
export class TextEditor {
private characters: Character[] = [];
private factory: CharacterFlyweightFactory;
constructor() {
this.factory = new CharacterFlyweightFactory();
}
addCharacter(char: string, font: string, fontSize: number, x: number, y: number): void {
this.characters.push({ char, font, fontSize, x, y });
console.log(`添加字符 "${char}" 到文档,位置=(${x}, ${y}), 字体="${font}", 字号=${fontSize}`);
}
renderDocument(): void {
console.log('开始渲染文档...');
this.characters.forEach(({ char, font, fontSize, x, y }) => {
const flyweight = this.factory.getFlyweight(char, font, fontSize);
flyweight.render({ x, y });
});
console.log(`文档渲染完成,共享字符对象数:${this.factory.getFlyweightCount()}`);
}
}
说明:TextEditor
作为客户端,维护外在状态(字符的x
、y
坐标),通过工厂获取共享的字符对象来渲染文档。
5. 运行示例 (main.ts)
import { TextEditor } from './editor';
// 创建文本编辑器
const editor = new TextEditor();
// 添加字符(模拟输入“Hello”)
editor.addCharacter('H', 'Arial', 12, 10, 20);
editor.addCharacter('e', 'Arial', 12, 15, 20);
editor.addCharacter('l', 'Arial', 12, 20, 20);
editor.addCharacter('l', 'Arial', 12, 25, 20);
editor.addCharacter('o', 'Arial', 12, 30, 20);
editor.addCharacter('H', 'Times', 14, 10, 30); // 不同字体
editor.addCharacter('e', 'Arial', 12, 15, 30); // 重复字符
// 渲染文档
editor.renderDocument();
6. 编译与运行
npx tsc
node dist/main.js
运行后,控制台输出类似:
添加字符 "H" 到文档,位置=(10, 20), 字体="Arial", 字号=12
创建轻量级字符:字符="H", 字体="Arial", 字号=12
添加字符 "e" 到文档,位置=(15, 20), 字体="Arial", 字号=12
创建轻量级字符:字符="e", 字体="Arial", 字号=12
添加字符 "l" 到文档,位置=(20, 20), 字体="Arial", 字号=12
创建轻量级字符:字符="l", 字体="Arial", 字号=12
添加字符 "l" 到文档,位置=(25, 20), 字体="Arial", 字号=12
添加字符 "o" 到文档,位置=(30, 20), 字体="Arial", 字号=12
创建轻量级字符:字符="o", 字体="Arial", 字号=12
添加字符 "H" 到文档,位置=(10, 30), 字体="Times", 字号=14
创建轻量级字符:字符="H", 字体="Times", 字号=14
添加字符 "e" 到文档,位置=(15, 30), 字体="Arial", 字号=12
开始渲染文档...
渲染字符 "H" 在位置 (10, 20), 字体="Arial", 字号=12
渲染字符 "e" 在位置 (15, 20), 字体="Arial", 字号=12
渲染字符 "l" 在位置 (20, 20), 字体="Arial", 字号=12
渲染字符 "l" 在位置 (25, 20), 字体="Arial", 字号=12
渲染字符 "o" 在位置 (30, 20), 字体="Arial", 字号=12
渲染字符 "H" 在位置 (10, 30), 字体="Times", 字号=14
渲染字符 "e" 在位置 (15, 30), 字体="Arial", 字号=12
文档渲染完成,共享字符对象数:5
解释输出:
- 输入7个字符(“H, e, l, l, o, H, e”),但只有5个独特的字符和样式组合(“H, Arial, 12”, “e, Arial, 12”, “l, Arial, 12”, “o, Arial, 12”, “H, Times, 14”)。
- 工厂确保重复的字符(如两个“l”和两个“e”)共享相同的轻量级对象,减少内存占用。
- 外在状态(
x
、y
坐标)由TextEditor
维护,动态传递给共享对象进行渲染。
总结
轻量级模式的优点在于其内存优化、性能提升、可扩展性和职责分离。通过共享内在状态(如字符的字体和样式),大幅减少对象数量和内存使用;减少对象创建开销,适合大规模对象场景;通过工厂模式易于添加新共享对象;内在和外在状态分离,逻辑清晰。该模式特别适用于大量相似对象、内存敏感系统、重复状态和渲染系统场景,如文本编辑器中的字符渲染、游戏中的精灵管理、AI任务指令系统和图形编辑器。