Skip to content

享元模式详解:概念、实现与应用

引言

享元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。

什么是享元模式?

享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。

核心思想

享元模式的核心思想是:

  1. 状态分离:将对象状态分为内部状态和外部状态
  2. 共享机制:通过共享内部状态来减少对象数量
  3. 内存优化:用较少的内存支持大量的对象

为什么需要享元模式?

在许多情况下,我们需要处理大量相似的对象:

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. 享元接口:定义享元对象的公共接口
  2. 具体享元:实现享元接口,存储内部状态
  3. 享元工厂:管理享元对象的创建和复用
  4. 状态分离:区分内部状态和外部状态
  5. 客户端:通过工厂获取享元对象

享元模式的实际应用场景

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) {
    // 释放完整对象
  }
}

享元模式的优缺点

优点

  1. 内存优化:大量减少对象数量,节省内存
  2. 性能提升:减少对象创建和垃圾回收开销
  3. 资源复用:有效复用共享资源
  4. 可扩展性:易于添加新的享元类型

缺点

  1. 复杂性增加:需要区分内部状态和外部状态
  2. 代码复杂:客户端需要维护外部状态
  3. 线程安全:共享对象需要考虑线程安全问题
  4. 适用场景有限:只适用于有大量相似对象的场景

总结

享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度的对象。享元模式摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能用较少的内存容量来支持大量的对象。

通过本章的学习,我们了解了:

  1. 享元模式的基本概念和核心思想
  2. 享元模式的实现方式和关键要点
  3. 享元模式在实际开发中的应用场景(文本编辑器、游戏粒子系统、数据库连接池)
  4. 享元模式与其他创建型模式的对比
  5. 享元模式的优缺点

享元模式在现代软件开发中应用广泛,特别是在需要处理大量相似对象、优化内存使用、提高系统性能的场景中,它可以很好地支持系统的效率和可扩展性。

在下一章中,我们将开始探讨行为型模式,首先是观察者模式。