享元模式(Flyweight Pattern)

C++ 享元模式(Flyweight Pattern)

一、模式基础认知

1. 模式定义

享元模式属于结构型设计模式 ,专门用于解决系统中大量重复细粒度对象 造成的内存资源浪费问题。

通过共享已有相同对象实例,避免重复创建同类对象,将海量相似对象压缩为少量共享对象,在保证业务功能不变的前提下,极大降低程序内存占用,提升程序运行效率。

2. 核心设计思想

  1. 对象复用思想:相同特征对象全局只创建一份,全系统共享调用
  2. 状态拆分原则 :严格拆分对象两大状态
    • 内部状态:固定不变、可全局共享,存储在享元对象内部
    • 外部状态:动态变化、不可共享,由调用方外部传入,不存入享元
  3. 工厂统一管控:通过专属享元工厂统一创建、缓存、查找共享对象
  4. 细粒度拆分:把复杂对象拆解为最小可共享单元,最大化实现共享效果

3. 四大标准核心角色

角色名称 英文标识 核心职责 C++ 代码实现规范
抽象享元类 Flyweight 定义所有共享对象统一业务接口,预留外部状态传入入口 纯虚基类,声明对外业务执行虚函数
具体享元类 ConcreteFlyweight 实现抽象接口,存储内部共享状态,执行业务逻辑 继承抽象享元,封装不变属性,接收外部状态完成行为
享元工厂类 FlyweightFactory 维护享元对象缓存池,负责创建、查找、复用共享对象 使用哈希容器存储实例,优先查找、不存在再创建
客户端 Client 维护管理外部动态状态,向工厂请求享元对象,调用业务方法 不直接new享元对象,全部通过工厂获取,传入动态参数

4. 内外状态详细区分

分类 内部状态(固有状态) 外部状态(可变状态)
特性 固定不变、全局一致 随时变动、每个场景不同
存储位置 存放在享元对象内部 由客户端单独维护传递
共享性 完全支持多场景共享 无法共享,独立独有
修改权限 运行期间禁止修改 可自由动态修改替换
常见示例 字体样式、树木种类、地形类型、字符样式 屏幕坐标、摆放位置、大小比例、渲染层级

二、模式适用场景与禁用场景

适用场景

  1. 系统中存在数量极大、属性高度相似的同类对象
  2. 嵌入式开发、移动端、游戏开发等内存资源极度受限项目
  3. 大量重复渲染场景:文字排版、地图地形、游戏植被、粒子特效
  4. 统一样式、不同位置的批量组件绘制
  5. 池化复用类需求:常量字符、通用图标、基础模板对象

不适用场景

  1. 对象之间差异极大,无共同内部状态,无法拆分共享
  2. 对象生命周期极短,创建销毁频率极低,优化收益极小
  3. 业务逻辑复杂,外部状态数量庞大,拆分状态成本过高
  4. 项目内存资源充足,无需做内存优化

三、享元模式优缺点分析

优点

  1. 极致节省内存:海量重复对象转为少量共享实例,大幅降低堆内存占用
  2. 提升程序性能:减少频繁new/delete对象带来的性能开销
  3. 统一对象管理:工厂集中管控所有共享对象,便于统一维护、批量配置
  4. 扩展性良好:新增具体享元类不改动原有核心代码,符合开闭原则
  5. 业务解耦:固定属性与动态属性分离,业务逻辑更加清晰

缺点

  1. 架构设计难度提升:必须精准拆分内外状态,设计不合理会导致模式失效
  2. 系统复杂度上升:多出工厂层、状态分层逻辑,代码可读性有所下降
  3. 线程安全问题:多线程并发获取享元对象时,缓存池存在线程争抢风险
  4. 共享对象不可随意修改:内部状态一旦修改,所有引用该对象的地方都会同步变化
  5. 外部状态管理繁琐:客户端需要单独维护所有可变外部状态,增加业务代码量

四、C++ 编码实现核心规范

  1. 抽象享元统一设计纯虚业务接口,参数用于接收外部可变状态
  2. 具体享元类只封装只读内部状态,不提供状态修改接口
  3. 享元工厂优先使用unordered_map构建对象缓存池,查找效率O(1)
  4. 全局统一使用std::shared_ptr智能指针管理享元对象生命周期
  5. 工厂提供唯一获取对象入口,禁止客户端直接实例化具体享元
  6. 多线程项目中,工厂获取对象逻辑添加互斥锁,保证线程安全
  7. 合理设计缓存Key,通过内部状态组合生成唯一键值,精准匹配共享对象

五、C++ 经典完整代码实现

示例1:文字字符渲染通用案例

cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;

// 抽象享元基类
class BaseCharFlyweight
{
public:
    virtual ~BaseCharFlyweight() = default;
    // posX posY 为外部动态状态
    virtual void showChar(int posX, int posY) const = 0;
};

// 具体享元类:文字字符(存储内部固定状态)
class ConcreteChar : public BaseCharFlyweight
{
private:
    char charVal;       // 内部状态:字符内容
    string fontFamily;  // 内部状态:字体
    int fontSize;       // 内部状态:字号
    string fontColor;   // 内部状态:字体颜色
public:
    ConcreteChar(char c, string font, int size, string color)
        : charVal(c), fontFamily(font), fontSize(size), fontColor(color) {}

    void showChar(int posX, int posY) const override
    {
        cout << "坐标(" << posX << "," << posY << ") "
             << "渲染字符:" << charVal
             << " 字体:" << fontFamily
             << " 字号:" << fontSize
             << " 颜色:" << fontColor << endl;
    }
};

// 享元工厂类
class CharFlyweightFactory
{
private:
    // 享元对象缓存池
    unordered_map<string, shared_ptr<BaseCharFlyweight>> flyPool;

    // 生成唯一缓存Key
    string createKey(char c, string font, int size, string color)
    {
        return string(1, c) + "_" + font + "_" + to_string(size) + "_" + color;
    }

public:
    // 获取享元对象
    shared_ptr<BaseCharFlyweight> getCharObj(char c, string font, int size, string color)
    {
        string key = createKey(c, font, size, color);
        // 缓存池中存在直接复用
        if (flyPool.find(key) != flyPool.end())
        {
            cout << "【复用已有享元对象】" << key << endl;
            return flyPool[key];
        }
        // 不存在则新建存入缓存
        cout << "【新建享元对象】" << key << endl;
        auto newObj = make_shared<ConcreteChar>(c, font, size, color);
        flyPool[key] = newObj;
        return newObj;
    }

    // 获取当前缓存享元总数
    size_t getFlyweightCount() const
    {
        return flyPool.size();
    }
};

// 客户端调用
int main()
{
    CharFlyweightFactory charFactory;

    // 重复获取相同字符对象,自动复用
    auto charA1 = charFactory.getCharObj('A', "微软雅黑", 16, "黑色");
    charA1->showChar(10, 20);

    auto charA2 = charFactory.getCharObj('A', "微软雅黑", 16, "黑色");
    charA2->showChar(30, 40);

    // 获取不同样式字符,创建新享元
    auto charB = charFactory.getCharObj('B', "宋体", 18, "红色");
    charB->showChar(50, 60);

    cout << "\n当前系统共享享元对象总数:" << charFactory.getFlyweightCount() << endl;
    return 0;
}

示例2:游戏地图地形渲染实战案例

cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <memory>
using namespace std;

// 抽象地形享元
class MapTileFlyweight
{
public:
    virtual ~MapTileFlyweight() = default;
    virtual void drawTile(int mapX, int mapY) const = 0;
};

// 具体享元:草地地形
class GrassTile : public MapTileFlyweight
{
public:
    void drawTile(int mapX, int mapY) const override
    {
        cout << "绘制草地地形 地图坐标:" << mapX << "," << mapY << endl;
    }
};

// 具体享元:水域地形
class WaterTile : public MapTileFlyweight
{
public:
    void drawTile(int mapX, int mapY) const override
    {
        cout << "绘制水域地形 地图坐标:" << mapX << "," << mapY << endl;
    }
};

// 具体享元:道路地形
class RoadTile : public MapTileFlyweight
{
public:
    void drawTile(int mapX, int mapY) const override
    {
        cout << "绘制道路地形 地图坐标:" << mapX << "," << mapY << endl;
    }
};

// 地形享元工厂
class MapTileFactory
{
private:
    unordered_map<string, shared_ptr<MapTileFlyweight>> tilePool;
public:
    shared_ptr<MapTileFlyweight> getMapTile(const string& tileType)
    {
        if (tilePool.count(tileType))
        {
            return tilePool[tileType];
        }
        if (tileType == "grass")
            tilePool[tileType] = make_shared<GrassTile>();
        else if (tileType == "water")
            tilePool[tileType] = make_shared<WaterTile>();
        else if (tileType == "road")
            tilePool[tileType] = make_shared<RoadTile>();
        return tilePool[tileType];
    }
};

// 客户端测试
int main()
{
    MapTileFactory tileFactory;
    // 全局复用同一个草地对象,渲染不同坐标
    auto grass = tileFactory.getMapTile("grass");
    grass->drawTile(1, 1);
    grass->drawTile(2, 3);
    grass->drawTile(5, 8);

    auto water = tileFactory.getMapTile("water");
    water->drawTile(10, 10);
    return 0;
}

六、享元模式与相近模式对比区分

1. 享元模式 VS 对象池模式

对比维度 享元模式 对象池模式
核心目的 节省内存,减少对象总数量 减少创建销毁开销,复用实例
使用方式 永久共享,全程不销毁 借出使用,用完归还
状态特性 内部状态固定不可修改 实例可重置状态,重复利用
适用场景 大量同质静态对象 频繁创建销毁的动态对象
典型案例 字体、地形、通用图标 数据库连接池、线程池、任务池

2. 享元模式 VS 单例模式

  • 单例:全局唯一一个对象,限制实例数量为1
  • 享元:分组共享,同类对象仅一份,不同类别可存在多个实例

七、C++ 项目开发最佳实践

  1. 项目目录分层规范

    FlyweightPattern/
    ├─ FlyweightBase.h // 抽象享元基类
    ├─ ConcreteFlyweight.h // 所有具体共享对象
    └─ FlyweightFactory.h // 享元统一工厂

  2. 业务层只依赖抽象享元与工厂类,隐藏所有具体实现类

  3. 对高频重复创建的工具类、渲染类优先采用享元优化

  4. 定期清理长期闲置无用享元对象,避免缓存内存堆积

  5. 严格隔离内外状态,禁止将动态业务数据存入共享对象内部

  6. 框架底层、通用基础组件优先使用享元模式做全局性能优化

八、模式核心总结

享元模式核心精髓:抽离固定内态共享,分离可变外态传入,工厂统一缓存复用

它是C++开发中专门用于内存优化的经典结构型模式,在游戏开发、图形渲染、嵌入式系统、海量数据排版等场景中使用率极高,用少量共享实例承载海量业务对象,在不破坏业务逻辑的前提下,实现程序内存与性能双重优化。

相关推荐
likerhood9 小时前
设计模式 · 享元模式(Flyweight Pattern)java
java·设计模式·享元模式
雪度娃娃7 天前
结构型设计模式——享元模式
c++·设计模式·享元模式
蜡笔小马7 天前
08.C++设计模式-享元模式
c++·设计模式·享元模式
多加点辣也没关系8 天前
设计模式-享元模式
数据库·设计模式·享元模式
sindyra12 天前
享元模式(Flyweight Pattern)
java·开发语言·设计模式·享元模式·优缺点
ximu_polaris1 个月前
设计模式(C++)-结构型模式-享元模式
c++·设计模式·享元模式
geovindu1 个月前
go: Flyweight Pattern
开发语言·设计模式·golang·享元模式
workflower1 个月前
机器人应用-楼宇室内巡逻
大数据·人工智能·算法·microsoft·机器人·动态规划·享元模式
Rsun045511 个月前
12、Java 享元模式从入门到实战
java·开发语言·享元模式