享元模式详解:概念、实现与应用
引言
享元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。
什么是享元模式?
享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。
核心思想
享元模式的核心思想是:
- 状态分离:将对象状态分为内部状态和外部状态
- 共享机制:通过共享内部状态来减少对象数量
- 内存优化:用较少的内存支持大量的对象
为什么需要享元模式?
在许多情况下,我们需要处理大量相似的对象:
1. 内存优化
当需要创建大量相似对象时:
- 每个对象占用一定的内存空间
- 大量对象导致内存消耗过大
- 需要优化内存使用
2. 性能提升
当对象创建和销毁频繁时:
- 对象创建和销毁带来性能开销
- 需要减少对象创建次数
- 提高系统整体性能
3. 资源共享
当多个对象可以共享某些状态时:
- 避免重复存储相同数据
- 提高资源利用效率
- 简化对象管理
享元模式的基本实现
让我们从一个简单的享元模式实现开始:
javascript
// 享元接口
class Flyweight {
operation(extrinsicState) {
throw new Error('必须实现 operation 方法');
}
}
// 具体享元
class ConcreteFlyweight extends Flyweight {
constructor(intrinsicState) {
super();
this.intrinsicState = intrinsicState; // 内部状态
}
operation(extrinsicState) {
return `具体享元: 内部状态=${this.intrinsicState}, 外部状态=${extrinsicState}`;
}
}
// 不共享的具体享元
class UnsharedConcreteFlyweight extends Flyweight {
operation(extrinsicState) {
return `不共享的具体享元: 外部状态=${extrinsicState}`;
}
}
// 享元工厂
class FlyweightFactory {
constructor() {
this.flyweights = new Map();
}
getFlyweight(key) {
if (!this.flyweights.has(key)) {
console.log(`享元工厂: 创建新的享元 ${key}`);
this.flyweights.set(key, new ConcreteFlyweight(key));
} else {
console.log(`享元工厂: 复用现有享元 ${key}`);
}
return this.flyweights.get(key);
}
getFlyweightCount() {
return this.flyweights.size;
}
}
// 客户端代码
function clientCode() {
const factory = new FlyweightFactory();
// 获取享元对象
const flyweight1 = factory.getFlyweight('A');
const flyweight2 = factory.getFlyweight('B');
const flyweight3 = factory.getFlyweight('A'); // 复用已有的享元
console.log(flyweight1.operation('外部状态1'));
console.log(flyweight2.operation('外部状态2'));
console.log(flyweight3.operation('外部状态3'));
console.log(`享元对象总数: ${factory.getFlyweightCount()}`);
}
clientCode();实现要点分析
- 享元接口:定义享元对象的公共接口
- 具体享元:实现享元接口,存储内部状态
- 享元工厂:管理享元对象的创建和复用
- 状态分离:区分内部状态和外部状态
- 客户端:通过工厂获取享元对象
享元模式的实际应用场景
1. 文本编辑器字符处理
在文本编辑器中,享元模式非常适合用于处理大量字符对象:
javascript
// 字符享元接口
class CharacterFlyweight {
constructor(character) {
this.character = character;
}
render(fontSize, fontColor, x, y) {
throw new Error('必须实现 render 方法');
}
getCharacter() {
return this.character;
}
}
// 具体字符享元
class ConcreteCharacterFlyweight extends CharacterFlyweight {
constructor(character) {
super(character);
// 内部状态:字符本身(共享)
this.character = character;
}
render(fontSize, fontColor, x, y) {
// 外部状态:字体大小、颜色、位置(不共享)
return `渲染字符 '${this.character}' 在位置(${x}, ${y}),字体大小: ${fontSize},颜色: ${fontColor}`;
}
}
// 字符属性(外部状态)
class CharacterProperties {
constructor(fontSize, fontColor, x, y) {
this.fontSize = fontSize;
this.fontColor = fontColor;
this.x = x;
this.y = y;
}
}
// 字符享元工厂
class CharacterFlyweightFactory {
constructor() {
this.flyweights = new Map();
}
getCharacterFlyweight(character) {
if (!this.flyweights.has(character)) {
console.log(`字符工厂: 创建字符享元 '${character}'`);
this.flyweights.set(character, new ConcreteCharacterFlyweight(character));
}
return this.flyweights.get(character);
}
getFlyweightCount() {
return this.flyweights.size;
}
}
// 文档字符
class DocumentCharacter {
constructor(character, properties, flyweightFactory) {
this.flyweight = flyweightFactory.getCharacterFlyweight(character);
this.properties = properties; // 外部状态
}
render() {
const { fontSize, fontColor, x, y } = this.properties;
return this.flyweight.render(fontSize, fontColor, x, y);
}
}
// 文本编辑器
class TextEditor {
constructor() {
this.flyweightFactory = new CharacterFlyweightFactory();
this.characters = [];
}
addCharacter(character, fontSize, fontColor, x, y) {
const properties = new CharacterProperties(fontSize, fontColor, x, y);
const documentCharacter = new DocumentCharacter(character, properties, this.flyweightFactory);
this.characters.push(documentCharacter);
}
renderDocument() {
console.log('=== 渲染文档 ===');
return this.characters.map(char => char.render());
}
getMemoryUsage() {
const flyweightCount = this.flyweightFactory.getFlyweightCount();
const characterCount = this.characters.length;
return {
flyweightCount: flyweightCount,
characterCount: characterCount,
memorySaved: characterCount - flyweightCount
};
}
}
// 使用示例
function textEditorExample() {
const editor = new TextEditor();
console.log('=== 添加文档字符 ===');
// 添加大量字符(很多重复字符)
const text = "Hello World! This is a sample text with many repeated characters.";
const fontSize = 12;
const fontColor = 'black';
for (let i = 0; i < text.length; i++) {
editor.addCharacter(text[i], fontSize, fontColor, i * 10, 0);
}
console.log('\n=== 渲染文档 ===');
const renderResults = editor.renderDocument();
renderResults.slice(0, 10).forEach(result => console.log(result)); // 只显示前10个
console.log(`... 还有 ${renderResults.length - 10} 个字符`);
console.log('\n=== 内存使用情况 ===');
const memoryUsage = editor.getMemoryUsage();
console.log(`字符种类数: ${memoryUsage.flyweightCount}`);
console.log(`总字符数: ${memoryUsage.characterCount}`);
console.log(`节省的内存对象数: ${memoryUsage.memorySaved}`);
// 添加更多字符来展示内存节省效果
console.log('\n=== 添加更多字符 ===');
for (let i = 0; i < 100; i++) {
editor.addCharacter('a', fontSize, fontColor, 0, i * 20);
}
const newMemoryUsage = editor.getMemoryUsage();
console.log(`添加100个'a'字符后:`);
console.log(`字符种类数: ${newMemoryUsage.flyweightCount}`);
console.log(`总字符数: ${newMemoryUsage.characterCount}`);
console.log(`节省的内存对象数: ${newMemoryUsage.memorySaved}`);
}
textEditorExample();2. 游戏中粒子系统
在游戏中,享元模式非常适合用于处理大量相似的游戏对象:
javascript
// 粒子享元接口
class ParticleFlyweight {
constructor(type) {
this.type = type;
}
render(x, y, color, size, velocity) {
throw new Error('必须实现 render 方法');
}
getType() {
return this.type;
}
}
// 具体粒子享元
class ConcreteParticleFlyweight extends ParticleFlyweight {
constructor(type, texture) {
super(type);
this.texture = texture; // 内部状态:纹理(共享)
}
render(x, y, color, size, velocity) {
// 外部状态:位置、颜色、大小、速度(不共享)
return `渲染${this.type}粒子: 位置(${x}, ${y}), 颜色${color}, 大小${size}, 速度${velocity}, 纹理${this.texture}`;
}
}
// 粒子属性(外部状态)
class ParticleProperties {
constructor(x, y, color, size, velocity) {
this.x = x;
this.y = y;
this.color = color;
this.size = size;
this.velocity = velocity;
}
}
// 粒子享元工厂
class ParticleFlyweightFactory {
constructor() {
this.flyweights = new Map();
this.initializeFlyweights();
}
initializeFlyweights() {
// 预先创建常见的粒子类型
this.flyweights.set('fire', new ConcreteParticleFlyweight('fire', '🔥'));
this.flyweights.set('water', new ConcreteParticleFlyweight('water', '💧'));
this.flyweights.set('smoke', new ConcreteParticleFlyweight('smoke', '💨'));
this.flyweights.set('spark', new ConcreteParticleFlyweight('spark', '✨'));
this.flyweights.set('snow', new ConcreteParticleFlyweight('snow', '❄️'));
}
getParticleFlyweight(type) {
if (!this.flyweights.has(type)) {
console.log(`粒子工厂: 创建新的粒子类型 ${type}`);
this.flyweights.set(type, new ConcreteParticleFlyweight(type, '❓'));
} else {
console.log(`粒子工厂: 复用现有粒子类型 ${type}`);
}
return this.flyweights.get(type);
}
getFlyweightCount() {
return this.flyweights.size;
}
}
// 粒子对象
class Particle {
constructor(type, properties, flyweightFactory) {
this.flyweight = flyweightFactory.getParticleFlyweight(type);
this.properties = properties; // 外部状态
}
update() {
// 更新粒子位置(简单的物理模拟)
this.properties.x += this.properties.velocity.x;
this.properties.y += this.properties.velocity.y;
this.properties.velocity.y += 0.1; // 重力效果
}
render() {
const { x, y, color, size, velocity } = this.properties;
return this.flyweight.render(x, y, color, size, velocity);
}
isAlive() {
// 简单的存活判断
return this.properties.y < 600; // 屏幕底部
}
}
// 粒子系统
class ParticleSystem {
constructor() {
this.flyweightFactory = new ParticleFlyweightFactory();
this.particles = [];
this.particleCounter = 0;
}
addParticle(type, x, y, color, size, velocity) {
const properties = new ParticleProperties(x, y, color, size, velocity);
const particle = new Particle(type, properties, this.flyweightFactory);
this.particles.push(particle);
this.particleCounter++;
}
update() {
// 更新所有粒子
for (let i = this.particles.length - 1; i >= 0; i--) {
this.particles[i].update();
// 移除死亡的粒子
if (!this.particles[i].isAlive()) {
this.particles.splice(i, 1);
}
}
}
render() {
console.log('=== 渲染粒子系统 ===');
return this.particles.map(particle => particle.render());
}
getStats() {
return {
activeParticles: this.particles.length,
totalCreated: this.particleCounter,
flyweightCount: this.flyweightFactory.getFlyweightCount(),
memorySaved: this.particleCounter - this.flyweightFactory.getFlyweightCount()
};
}
createExplosion(x, y) {
const types = ['fire', 'spark', 'smoke'];
const colors = ['red', 'orange', 'yellow', 'gray'];
for (let i = 0; i < 50; i++) {
const type = types[Math.floor(Math.random() * types.length)];
const color = colors[Math.floor(Math.random() * colors.length)];
const size = Math.random() * 10 + 5;
const velocity = {
x: (Math.random() - 0.5) * 10,
y: (Math.random() - 0.5) * 10 - 5
};
this.addParticle(type, x, y, color, size, velocity);
}
}
}
// 使用示例
function particleSystemExample() {
const particleSystem = new ParticleSystem();
console.log('=== 创建粒子效果 ===');
// 创建不同类型的粒子
particleSystem.addParticle('fire', 100, 100, 'red', 10, { x: 1, y: -2 });
particleSystem.addParticle('water', 150, 100, 'blue', 8, { x: -1, y: -1 });
particleSystem.addParticle('smoke', 200, 100, 'gray', 12, { x: 0, y: -0.5 });
particleSystem.addParticle('spark', 250, 100, 'yellow', 5, { x: 2, y: -3 });
particleSystem.addParticle('snow', 300, 100, 'white', 6, { x: 0.5, y: 0.5 });
console.log('\n=== 渲染粒子 ===');
const renderResults = particleSystem.render();
renderResults.forEach(result => console.log(result));
console.log('\n=== 粒子系统统计 ===');
const stats = particleSystem.getStats();
console.log(`活跃粒子数: ${stats.activeParticles}`);
console.log(`创建的粒子总数: ${stats.totalCreated}`);
console.log(`享元对象数: ${stats.flyweightCount}`);
console.log(`节省的内存对象数: ${stats.memorySaved}`);
console.log('\n=== 创建爆炸效果 ===');
particleSystem.createExplosion(400, 300);
console.log('\n=== 更新粒子系统 ===');
particleSystem.update();
console.log('\n=== 更新后统计 ===');
const newStats = particleSystem.getStats();
console.log(`活跃粒子数: ${newStats.activeParticles}`);
console.log(`创建的粒子总数: ${newStats.totalCreated}`);
console.log(`享元对象数: ${newStats.flyweightCount}`);
console.log(`节省的内存对象数: ${newStats.memorySaved}`);
}
particleSystemExample();3. 数据库连接池
在数据库访问中,享元模式非常适合用于实现连接池:
javascript
// 数据库连接享元接口
class DatabaseConnectionFlyweight {
constructor(connectionId) {
this.connectionId = connectionId;
}
executeQuery(sql, parameters) {
throw new Error('必须实现 executeQuery 方法');
}
executeUpdate(sql, parameters) {
throw new Error('必须实现 executeUpdate 方法');
}
getConnectionId() {
return this.connectionId;
}
isActive() {
throw new Error('必须实现 isActive 方法');
}
activate() {
throw new Error('必须实现 activate 方法');
}
deactivate() {
throw new Error('必须实现 deactivate 方法');
}
}
// 具体数据库连接享元
class ConcreteDatabaseConnection extends DatabaseConnectionFlyweight {
constructor(connectionId) {
super(connectionId);
this.active = false;
this.lastUsed = Date.now();
// 模拟数据库连接的内部状态
this.connectionString = `db://connection-${connectionId}`;
}
executeQuery(sql, parameters = []) {
if (!this.active) {
throw new Error(`连接 ${this.connectionId} 未激活`);
}
this.lastUsed = Date.now();
console.log(`连接 ${this.connectionId}: 执行查询 "${sql}" with parameters: ${JSON.stringify(parameters)}`);
// 模拟查询结果
return {
rows: [
{ id: 1, name: '记录1' },
{ id: 2, name: '记录2' }
],
rowCount: 2
};
}
executeUpdate(sql, parameters = []) {
if (!this.active) {
throw new Error(`连接 ${this.connectionId} 未激活`);
}
this.lastUsed = Date.now();
console.log(`连接 ${this.connectionId}: 执行更新 "${sql}" with parameters: ${JSON.stringify(parameters)}`);
// 模拟更新结果
return {
affectedRows: 1,
insertId: 3
};
}
isActive() {
return this.active;
}
activate() {
this.active = true;
this.lastUsed = Date.now();
console.log(`连接 ${this.connectionId}: 激活连接`);
}
deactivate() {
this.active = false;
console.log(`连接 ${this.connectionId}: 释放连接`);
}
getLastUsed() {
return this.lastUsed;
}
}
// 数据库连接池
class DatabaseConnectionPool {
constructor(maxConnections = 10) {
this.maxConnections = maxConnections;
this.availableConnections = [];
this.usedConnections = new Set();
this.connectionCounter = 0;
}
getConnection() {
let connection;
// 如果有可用连接,复用它
if (this.availableConnections.length > 0) {
connection = this.availableConnections.pop();
console.log(`连接池: 复用现有连接 ${connection.getConnectionId()}`);
} else if (this.usedConnections.size < this.maxConnections) {
// 如果没达到最大连接数,创建新连接
this.connectionCounter++;
connection = new ConcreteDatabaseConnection(this.connectionCounter);
console.log(`连接池: 创建新连接 ${connection.getConnectionId()}`);
} else {
// 如果达到最大连接数,等待或抛出异常
throw new Error('连接池已满,无法获取新连接');
}
connection.activate();
this.usedConnections.add(connection);
return connection;
}
releaseConnection(connection) {
if (this.usedConnections.has(connection)) {
connection.deactivate();
this.usedConnections.delete(connection);
this.availableConnections.push(connection);
console.log(`连接池: 释放连接 ${connection.getConnectionId()} 回到池中`);
}
}
getPoolStats() {
return {
maxConnections: this.maxConnections,
availableConnections: this.availableConnections.length,
usedConnections: this.usedConnections.size,
totalCreated: this.connectionCounter
};
}
closeAllConnections() {
// 关闭所有连接
for (const connection of this.availableConnections) {
console.log(`连接池: 关闭连接 ${connection.getConnectionId()}`);
}
for (const connection of this.usedConnections) {
connection.deactivate();
console.log(`连接池: 关闭连接 ${connection.getConnectionId()}`);
}
this.availableConnections = [];
this.usedConnections.clear();
}
}
// 数据库操作管理器
class DatabaseManager {
constructor(connectionPool) {
this.connectionPool = connectionPool;
}
async query(sql, parameters = []) {
let connection;
try {
connection = this.connectionPool.getConnection();
const result = connection.executeQuery(sql, parameters);
return result;
} finally {
if (connection) {
this.connectionPool.releaseConnection(connection);
}
}
}
async update(sql, parameters = []) {
let connection;
try {
connection = this.connectionPool.getConnection();
const result = connection.executeUpdate(sql, parameters);
return result;
} finally {
if (connection) {
this.connectionPool.releaseConnection(connection);
}
}
}
getStats() {
return this.connectionPool.getPoolStats();
}
}
// 使用示例
async function databasePoolExample() {
const connectionPool = new DatabaseConnectionPool(5);
const dbManager = new DatabaseManager(connectionPool);
console.log('=== 数据库连接池示例 ===');
try {
console.log('\n=== 执行查询操作 ===');
const result1 = await dbManager.query('SELECT * FROM users WHERE id = ?', [1]);
console.log('查询结果:', result1);
const result2 = await dbManager.query('SELECT * FROM products WHERE category = ?', ['electronics']);
console.log('查询结果:', result2);
console.log('\n=== 执行更新操作 ===');
const updateResult = await dbManager.update('UPDATE users SET name = ? WHERE id = ?', ['新名字', 1]);
console.log('更新结果:', updateResult);
console.log('\n=== 连接池统计 ===');
const stats = dbManager.getStats();
console.log(`最大连接数: ${stats.maxConnections}`);
console.log(`可用连接数: ${stats.availableConnections}`);
console.log(`使用中连接数: ${stats.usedConnections}`);
console.log(`创建的连接总数: ${stats.totalCreated}`);
console.log('\n=== 并发操作示例 ===');
// 模拟并发操作
const promises = [];
for (let i = 0; i < 3; i++) {
promises.push(
dbManager.query('SELECT * FROM users LIMIT 10')
.then(result => console.log(`并发查询 ${i + 1} 完成`))
);
}
await Promise.all(promises);
console.log('\n=== 并发后统计 ===');
const newStats = dbManager.getStats();
console.log(`可用连接数: ${newStats.availableConnections}`);
console.log(`使用中连接数: ${newStats.usedConnections}`);
} catch (error) {
console.error('数据库操作失败:', error.message);
} finally {
console.log('\n=== 关闭所有连接 ===');
connectionPool.closeAllConnections();
}
}
// 运行示例
databasePoolExample();享元模式的关键要点
1. 状态分离
javascript
// 内部状态(共享)
class Flyweight {
constructor(intrinsicState) {
this.intrinsicState = intrinsicState; // 共享状态
}
}
// 外部状态(不共享)
class Context {
constructor(extrinsicState) {
this.extrinsicState = extrinsicState; // 不共享状态
}
}2. 工厂管理
javascript
class FlyweightFactory {
constructor() {
this.flyweights = new Map();
}
getFlyweight(key) {
if (!this.flyweights.has(key)) {
this.flyweights.set(key, new ConcreteFlyweight(key));
}
return this.flyweights.get(key);
}
}3. 客户端协调
javascript
class Client {
constructor(factory) {
this.factory = factory;
}
operation(intrinsicState, extrinsicState) {
const flyweight = this.factory.getFlyweight(intrinsicState);
return flyweight.operation(extrinsicState);
}
}享元模式与其它模式的对比
享元模式 vs 单例模式
javascript
// 享元模式 - 共享多个对象
class FlyweightFactory {
getFlyweight(key) {
// 根据键值返回共享对象
}
}
// 单例模式 - 确保一个类只有一个实例
class Singleton {
static getInstance() {
// 确保只有一个实例
}
}享元模式 vs 对象池模式
javascript
// 享元模式 - 共享对象状态
class Flyweight {
operation(extrinsicState) {
// 共享内部状态,传入外部状态
}
}
// 对象池模式 - 复用完整对象
class ObjectPool {
acquire() {
// 获取完整对象
}
release(object) {
// 释放完整对象
}
}享元模式的优缺点
优点
- 内存优化:大量减少对象数量,节省内存
- 性能提升:减少对象创建和垃圾回收开销
- 资源复用:有效复用共享资源
- 可扩展性:易于添加新的享元类型
缺点
- 复杂性增加:需要区分内部状态和外部状态
- 代码复杂:客户端需要维护外部状态
- 线程安全:共享对象需要考虑线程安全问题
- 适用场景有限:只适用于有大量相似对象的场景
总结
享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。
通过本章的学习,我们了解了:
- 享元模式的基本概念和核心思想
- 享元模式的实现方式和关键要点
- 享元模式在实际开发中的应用场景(文本编辑器、游戏粒子系统、数据库连接池)
- 享元模式与其他创建型模式的对比
- 享元模式的优缺点
享元模式在现代软件开发中应用广泛,特别是在需要处理大量相似对象、优化内存使用、提高系统性能的场景中,它可以很好地支持系统的效率和可扩展性。
在下一章中,我们将开始探讨行为型模式,首先是观察者模式。