结构型设计模式的一种,它通过就是享元模式(Flyweight)共享细粒度对象来减少内存占用和提高性能,特别适合处理大量相似对象的场景(如文字处理中的字符、游戏中的粒子效果)。

一、核心思想与角色

“就是享元模式的核心区分内部状态与外部状态,共享内部状态”,从而减少对象数量。其核心角色如下:

角色名称核心职责
抽象享元(Flyweight)定义享元对象的接口,声明接收外部状态的方法。
具体享元(ConcreteFlyweight)建立抽象享元接口,存储内部状态(可共享),不存储外部状态(需从客户端传入)。
非享元(UnsharedConcreteFlyweight)不参与共享的享元子类,通常包含不能共享的外部状态。
享元工厂(FlyweightFactory)管理享元对象池,负责创建和复用享元对象,确保相同内部状态的对象只存在一个实例。
客户端(Client)维护享元对象的外部状态,通过工厂获取享元对象并使用。

核心思想:将对象的状态分为可共享的内部状态(如字符的字体、大小)和不可共享的外部状态(如字符的位置),通过共享内部状态减少对象数量,外部状态由客户端在使用时传入。

二、实现示例(文字处理框架)

假设大家需要实现一个文字处理系统,文档中涵盖大量字符(如字母、数字),相同字符(如多个’A’)具有相同的字体和大小(内部状态),但位置不同(外部状态)。使用享元模式可大幅减少字符对象数量:

#include <iostream>
  #include <string>
    #include <map>
      #include <vector>
        // 外部状态:字符位置(不共享)
        struct Position {
        int x; // 横坐标
        int y; // 纵坐标
        Position(int x_, int y_) : x(x_), y(y_) {}
        };
        // 1. 抽象享元:字符
        class CharacterFlyweight {
        public:
        // 纯虚方法:显示字符(需要外部状态position)
        virtual void display(const Position& position) const = 0;
        virtual ~CharacterFlyweight() = default;
        };
        // 2. 具体享元:具体字符(如'A'、'B')
        class ConcreteCharacter : public CharacterFlyweight {
        private:
        char symbol;       // 字符符号(内部状态)
        std::string font;  // 字体(内部状态)
        int size;          // 大小(内部状态)
        public:
        // 构造函数:初始化内部状态(可共享)
        ConcreteCharacter(char s, const std::string& f, int sz)
        : symbol(s), font(f), size(sz) {}
        // 显示字符:结合外部状态(位置)
        void display(const Position& position) const override {
        std::cout << "字符 '" << symbol
        << "'(字体:" << font
        << ",大小:" << size
        << ")位于 (" << position.x << "," << position.y << ")" << std::endl;
        }
        };
        // 3. 享元工厂:字符工厂
        class CharacterFactory {
        private:
        // 享元池:存储共享的字符对象(key为"符号+字体+大小"的组合)
        std::map<std::string, CharacterFlyweight*> flyweights;
          public:
          // 获取享元对象:存在则复用,不存在则创建
          CharacterFlyweight* getCharacter(char symbol, const std::string& font, int size) {
          // 生成唯一键(内部状态组合)
          std::string key = std::string(1, symbol) + "|" + font + "|" + std::to_string(size);
          // 检查池中是否存在
          if (flyweights.find(key) == flyweights.end()) {
          // 不存在则创建新对象并加入池
          flyweights[key] = new ConcreteCharacter(symbol, font, size);
          std::cout << "创建新享元:" << key << std::endl;
          } else {
          std::cout << "复用享元:" << key << std::endl;
          }
          return flyweights[key];
          }
          // 析构函数:释放所有享元对象
          ~CharacterFactory() {
          for (auto& pair : flyweights) {
          delete pair.second;
          }
          }
          };
          // 客户端代码:文档处理
          int main() {
          // 创建享元工厂
          CharacterFactory factory;
          // 文档内容:多个相同字符(共享内部状态)
          std::vector<std::pair<CharacterFlyweight*, Position>> document;
            // 添加字符到文档(相同"符号+字体+大小"会复用享元)
            document.emplace_back(
            factory.getCharacter('A', "Arial", 12),
            Position(10, 20)
            );
            document.emplace_back(
            factory.getCharacter('A', "Arial", 12),  // 复用上面的'A'
            Position(30, 20)
            );
            document.emplace_back(
            factory.getCharacter('B', "Arial", 12),  // 新享元
            Position(50, 20)
            );
            document.emplace_back(
            factory.getCharacter('A', "Times", 14),  // 新享元(字体和大小不同)
            Position(10, 40)
            );
            document.emplace_back(
            factory.getCharacter('A', "Times", 14),  // 复用上面的'A'
            Position(30, 40)
            );
            // 显示文档中所有字符(结合外部状态)
            std::cout << "\n=== 文档内容 ===" << std::endl;
            for (const auto& item : document) {
            item.first->display(item.second);
            }
            return 0;
            }

三、代码解析

  1. 内部状态与外部状态

    • 内部状态ConcreteCharacter中的symbol(字符)、font(字体)、size(大小),这些属性相同的字符可以共享。
    • 外部状态Position中的xy(位置),每个字符的位置不同,无法共享,由客户端在使用时传入。
  2. 抽象享元(CharacterFlyweight)
    定义了display()方法,参数为外部状态Position,确保所有享元对象都能接收外部状态。

  3. 具体享元(ConcreteCharacter)
    存储内部状态,实现display()方法,将内部状态与传入的外部状态结合使用(显示字符及其位置)。

  4. 享元工厂(CharacterFactory)

    • 维护一个flyweights池(map容器),键为内部状态的组合(符号|字体|大小),值为对应的享元对象。
    • getCharacter()方法:当请求的字符已存在时复用,不存在时创建新对象并加入池,确保相同内部状态的对象只存在一个。
  5. 客户端使用
    客户端通过工厂获取享元对象,维护外部状态(位置),并调用display()方法时传入外部状态,实现字符的显示。

四、核心优势与适用场景

优势
  1. 减少内存占用:通过共享相同内部状态的对象,大幅减少系统中的对象数量(如示例中5个字符实际只创建3个享元对象)。
  2. 提高性能:减少对象创建和销毁的开销,尤其适合大量相似对象的场景。
  3. 分离状态:明确区分内部状态(可共享)和外部状态(不可共享),使体系设计更清晰。
适用场景
  1. 存在大量相似对象:如文字处理中的字符、游戏中的粒子(火焰、雨滴)、电商系统中的商品规格。
  2. 对象的大部分状态可共享:内部状态占比高,外部状态占比低,共享收益明显。
  3. 需要缓存对象复用:通过工厂池管理对象,避免重复创建。

五、与其他模式的区别

模式核心差异点
享元模式共享相似对象的内部状态,减少内存占用,关注“对象复用”。
单例模式确保一个类只有一个实例,关注“唯一性”,不涉及状态共享。
原型模式通过克隆创建对象,关注“快捷复制”,不强调状态共享。
工厂模式负责对象创建,不涉及对象复用,享元模式中的工厂是特殊的“缓存工厂”。

六、实践建议

  1. 明确状态划分享元模式的关键。就是:清晰区分内部状态(不变、可共享)和外部状态(可变、不可共享),这
  2. 工厂池设计:享元工厂应高效管理对象池(如用hash map存储),确保快速查找和复用。
  3. 外部状态处理:外部状态由客户端管理,避免享元对象存储外部状态导致无法共享。
  4. 线程安全:多线程环境下,需为享元工厂的get方法添加同步机制,避免并发创建重复对象。

享元模式的核心价值在于“通过共享减少对象数量处理“大量细粒度对象”场景的最佳设计模式。就是”,当平台中存在大量相似对象且内存占用过高时,它能显著优化资源应用。其关键是合理划分内部状态和外部状态,通过工厂实现对象复用,

posted on 2025-10-11 11:51  ycfenxi  阅读(8)  评论(0)    收藏  举报