享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用,从而减少内存消耗和提高系统性能。这种模式特别适合当系统中存在大量相似对象,且这些对象的大部分状态可以共享的场景。

享元模式的核心角色

  1. 享元(Flyweight):定义共享对象的接口,包含可以被多个对象共享的内部状态
  2. 具体享元(Concrete Flyweight):实现享元接口,存储内部状态
  3. 非享元(Unshared Flyweight):不参与共享的对象,通常包含随环境变化的外部状态
  4. 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保合理共享

享元模式的实现示例

下面以"文字处理系统"为例展示享元模式的实现,文档中的字符(如字母、数字)是大量重复的细粒度对象,我们可以共享这些字符的样式信息(内部状态),只保留位置等独特信息(外部状态):

#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

// 享元:字符样式(内部状态)
class CharacterStyle {
private:
    std::string font;    // 字体
    int size;            // 字号
    std::string color;   // 颜色

public:
    CharacterStyle(const std::string& f, int s, const std::string& c)
        : font(f), size(s), color(c) {}

    // 显示样式信息
    void showStyle() const {
        std::cout << "[" << font << ", " << size << "pt, " << color << "]";
    }

    // 用于在工厂中作为键值比较
    bool operator==(const CharacterStyle& other) const {
        return font == other.font && size == other.size && color == other.color;
    }
};

// 为CharacterStyle定义哈希函数,使其可作为unordered_map的键
namespace std {
    template<> struct hash<CharacterStyle> {
        size_t operator()(const CharacterStyle& s) const {
            size_t h1 = hash<string>()(s.font);
            size_t h2 = hash<int>()(s.size);
            size_t h3 = hash<string>()(s.color);
            return h1 ^ (h2 << 1) ^ (h3 << 2);
        }
    };
}

// 享元工厂:管理字符样式
class StyleFactory {
private:
    std::unordered_map<CharacterStyle, CharacterStyle*> styles;

public:
    ~StyleFactory() {
        // 释放所有共享的样式对象
        for (auto& pair : styles) {
            delete pair.second;
        }
    }

    // 获取或创建共享样式
    CharacterStyle* getStyle(const std::string& font, int size, const std::string& color) {
        CharacterStyle tempStyle(font, size, color);
        
        // 如果样式已存在,则返回已有的
        if (styles.find(tempStyle) != styles.end()) {
            return styles[tempStyle];
        }
        
        // 否则创建新样式并存储
        CharacterStyle* newStyle = new CharacterStyle(font, size, color);
        styles[tempStyle] = newStyle;
        return newStyle;
    }

    // 显示当前缓存的样式数量
    void showStyleCount() const {
        std::cout << "当前缓存的样式数量: " << styles.size() << std::endl;
    }
};

// 字符类:包含共享的样式(享元)和独特的位置(外部状态)
class Character {
private:
    char c;                     // 字符内容
    CharacterStyle* style;      // 共享的样式(内部状态)
    int x, y;                   // 位置(外部状态)

public:
    Character(char ch, CharacterStyle* s, int xPos, int yPos)
        : c(ch), style(s), x(xPos), y(yPos) {}

    // 显示字符及其属性
    void display() const {
        std::cout << "字符 '" << c << "' 位置: (" << x << "," << y << ") 样式: ";
        style->showStyle();
        std::cout << std::endl;
    }
};

// 文档类:包含多个字符
class Document {
private:
    std::vector<Character> characters;

public:
    void addCharacter(char c, CharacterStyle* style, int x, int y) {
        characters.emplace_back(c, style, x, y);
    }

    void display() const {
        std::cout << "\n文档内容如下:" << std::endl;
        for (const auto& ch : characters) {
            ch.display();
        }
    }
};

// 客户端使用
int main() {
    // 创建样式工厂
    StyleFactory styleFactory;
    
    // 创建文档
    Document doc;
    
    // 向文档添加字符(大量重复使用相同样式)
    CharacterStyle* style1 = styleFactory.getStyle("Arial", 12, "black");
    doc.addCharacter('H', style1, 10, 20);
    doc.addCharacter('e', style1, 20, 20);
    doc.addCharacter('l', style1, 30, 20);
    doc.addCharacter('l', style1, 40, 20);
    doc.addCharacter('o', style1, 50, 20);
    
    CharacterStyle* style2 = styleFactory.getStyle("Times New Roman", 14, "red");
    doc.addCharacter('W', style2, 10, 40);
    doc.addCharacter('o', style2, 25, 40);
    doc.addCharacter('r', style2, 40, 40);
    doc.addCharacter('l', style2, 55, 40);
    doc.addCharacter('d', style2, 70, 40);
    
    // 再次使用已有的样式
    doc.addCharacter('!', style1, 60, 20);
    doc.addCharacter('!', style2, 85, 40);
    
    // 显示缓存的样式数量(应该只有2种)
    styleFactory.showStyleCount();
    
    // 显示文档内容
    doc.display();
    
    return 0;
}

享元模式的核心概念

  • 内部状态(Intrinsic State):可以被多个对象共享的状态,不随环境变化(如示例中的字体、字号、颜色)
  • 外部状态(Extrinsic State):随环境变化的状态,不能被共享,由客户端保存和传递(如示例中的字符位置)

享元模式通过分离这两种状态,使相同内部状态的对象可以被共享,从而大幅减少系统中的对象数量。

享元模式的工作流程

  1. 享元工厂创建并管理享元对象,确保相同内部状态的对象只被创建一次
  2. 客户端向工厂请求享元对象,提供内部状态
  3. 客户端保存并管理外部状态,在使用享元对象时将外部状态传递给它
  4. 享元对象结合内部状态和客户端提供的外部状态完成操作

享元模式的应用场景

  1. 系统中存在大量相似对象,且这些对象消耗大量内存
  2. 对象的大部分状态可以共享(内部状态),只有少量状态是独特的(外部状态)
  3. 需要缓冲池的场景(如数据库连接池、线程池)
  4. 字符串常量池、缓存系统等

享元模式的优缺点

优点

  • 减少了系统中的对象数量,降低内存消耗
  • 提高了系统性能,减少了对象创建和销毁的开销
  • 通过共享对象,提高了资源利用率

缺点

  • 增加了系统复杂度,需要分离内部状态和外部状态
  • 外部状态由客户端管理,可能会增加客户端的负担
  • 共享对象不能有独立的生命周期,管理不当可能导致问题

享元模式在C++标准库中也有应用,例如std::string的字符串常量池,多个相同的字符串字面量会共享同一份内存;此外,某些GUI库中的按钮、图标等组件也常使用享元模式来优化性能。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐