C++ 享元模式(Flyweight Pattern)
一、模式基础认知
1. 模式定义
享元模式属于结构型设计模式 ,专门用于解决系统中大量重复细粒度对象 造成的内存资源浪费问题。
通过共享已有相同对象实例,避免重复创建同类对象,将海量相似对象压缩为少量共享对象,在保证业务功能不变的前提下,极大降低程序内存占用,提升程序运行效率。
2. 核心设计思想
- 对象复用思想:相同特征对象全局只创建一份,全系统共享调用
- 状态拆分原则 :严格拆分对象两大状态
- 内部状态:固定不变、可全局共享,存储在享元对象内部
- 外部状态:动态变化、不可共享,由调用方外部传入,不存入享元
- 工厂统一管控:通过专属享元工厂统一创建、缓存、查找共享对象
- 细粒度拆分:把复杂对象拆解为最小可共享单元,最大化实现共享效果
3. 四大标准核心角色
| 角色名称 | 英文标识 | 核心职责 | C++ 代码实现规范 |
|---|---|---|---|
| 抽象享元类 | Flyweight | 定义所有共享对象统一业务接口,预留外部状态传入入口 | 纯虚基类,声明对外业务执行虚函数 |
| 具体享元类 | ConcreteFlyweight | 实现抽象接口,存储内部共享状态,执行业务逻辑 | 继承抽象享元,封装不变属性,接收外部状态完成行为 |
| 享元工厂类 | FlyweightFactory | 维护享元对象缓存池,负责创建、查找、复用共享对象 | 使用哈希容器存储实例,优先查找、不存在再创建 |
| 客户端 | Client | 维护管理外部动态状态,向工厂请求享元对象,调用业务方法 | 不直接new享元对象,全部通过工厂获取,传入动态参数 |
4. 内外状态详细区分
| 分类 | 内部状态(固有状态) | 外部状态(可变状态) |
|---|---|---|
| 特性 | 固定不变、全局一致 | 随时变动、每个场景不同 |
| 存储位置 | 存放在享元对象内部 | 由客户端单独维护传递 |
| 共享性 | 完全支持多场景共享 | 无法共享,独立独有 |
| 修改权限 | 运行期间禁止修改 | 可自由动态修改替换 |
| 常见示例 | 字体样式、树木种类、地形类型、字符样式 | 屏幕坐标、摆放位置、大小比例、渲染层级 |
二、模式适用场景与禁用场景
适用场景
- 系统中存在数量极大、属性高度相似的同类对象
- 嵌入式开发、移动端、游戏开发等内存资源极度受限项目
- 大量重复渲染场景:文字排版、地图地形、游戏植被、粒子特效
- 统一样式、不同位置的批量组件绘制
- 池化复用类需求:常量字符、通用图标、基础模板对象
不适用场景
- 对象之间差异极大,无共同内部状态,无法拆分共享
- 对象生命周期极短,创建销毁频率极低,优化收益极小
- 业务逻辑复杂,外部状态数量庞大,拆分状态成本过高
- 项目内存资源充足,无需做内存优化
三、享元模式优缺点分析
优点
- 极致节省内存:海量重复对象转为少量共享实例,大幅降低堆内存占用
- 提升程序性能:减少频繁new/delete对象带来的性能开销
- 统一对象管理:工厂集中管控所有共享对象,便于统一维护、批量配置
- 扩展性良好:新增具体享元类不改动原有核心代码,符合开闭原则
- 业务解耦:固定属性与动态属性分离,业务逻辑更加清晰
缺点
- 架构设计难度提升:必须精准拆分内外状态,设计不合理会导致模式失效
- 系统复杂度上升:多出工厂层、状态分层逻辑,代码可读性有所下降
- 线程安全问题:多线程并发获取享元对象时,缓存池存在线程争抢风险
- 共享对象不可随意修改:内部状态一旦修改,所有引用该对象的地方都会同步变化
- 外部状态管理繁琐:客户端需要单独维护所有可变外部状态,增加业务代码量
四、C++ 编码实现核心规范
- 抽象享元统一设计纯虚业务接口,参数用于接收外部可变状态
- 具体享元类只封装只读内部状态,不提供状态修改接口
- 享元工厂优先使用
unordered_map构建对象缓存池,查找效率O(1) - 全局统一使用
std::shared_ptr智能指针管理享元对象生命周期 - 工厂提供唯一获取对象入口,禁止客户端直接实例化具体享元
- 多线程项目中,工厂获取对象逻辑添加互斥锁,保证线程安全
- 合理设计缓存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++ 项目开发最佳实践
-
项目目录分层规范
FlyweightPattern/
├─ FlyweightBase.h // 抽象享元基类
├─ ConcreteFlyweight.h // 所有具体共享对象
└─ FlyweightFactory.h // 享元统一工厂 -
业务层只依赖抽象享元与工厂类,隐藏所有具体实现类
-
对高频重复创建的工具类、渲染类优先采用享元优化
-
定期清理长期闲置无用享元对象,避免缓存内存堆积
-
严格隔离内外状态,禁止将动态业务数据存入共享对象内部
-
框架底层、通用基础组件优先使用享元模式做全局性能优化
八、模式核心总结
享元模式核心精髓:抽离固定内态共享,分离可变外态传入,工厂统一缓存复用
它是C++开发中专门用于内存优化的经典结构型模式,在游戏开发、图形渲染、嵌入式系统、海量数据排版等场景中使用率极高,用少量共享实例承载海量业务对象,在不破坏业务逻辑的前提下,实现程序内存与性能双重优化。