结构型设计模式的一种,它通过就是享元模式(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;
}
三、代码解析
内部状态与外部状态:
- 内部状态:
ConcreteCharacter中的symbol(字符)、font(字体)、size(大小),这些属性相同的字符可以共享。 - 外部状态:
Position中的x和y(位置),每个字符的位置不同,无法共享,由客户端在使用时传入。
- 内部状态:
抽象享元(CharacterFlyweight):
定义了display()方法,参数为外部状态Position,确保所有享元对象都能接收外部状态。具体享元(ConcreteCharacter):
存储内部状态,实现display()方法,将内部状态与传入的外部状态结合使用(显示字符及其位置)。享元工厂(CharacterFactory):
- 维护一个
flyweights池(map容器),键为内部状态的组合(符号|字体|大小),值为对应的享元对象。 getCharacter()方法:当请求的字符已存在时复用,不存在时创建新对象并加入池,确保相同内部状态的对象只存在一个。
- 维护一个
客户端使用:
客户端通过工厂获取享元对象,维护外部状态(位置),并调用display()方法时传入外部状态,实现字符的显示。
四、核心优势与适用场景
优势
- 减少内存占用:通过共享相同内部状态的对象,大幅减少系统中的对象数量(如示例中5个字符实际只创建3个享元对象)。
- 提高性能:减少对象创建和销毁的开销,尤其适合大量相似对象的场景。
- 分离状态:明确区分内部状态(可共享)和外部状态(不可共享),使体系设计更清晰。
适用场景
- 存在大量相似对象:如文字处理中的字符、游戏中的粒子(火焰、雨滴)、电商系统中的商品规格。
- 对象的大部分状态可共享:内部状态占比高,外部状态占比低,共享收益明显。
- 需要缓存对象复用:通过工厂池管理对象,避免重复创建。
五、与其他模式的区别
| 模式 | 核心差异点 |
|---|---|
| 享元模式 | 共享相似对象的内部状态,减少内存占用,关注“对象复用”。 |
| 单例模式 | 确保一个类只有一个实例,关注“唯一性”,不涉及状态共享。 |
| 原型模式 | 通过克隆创建对象,关注“快捷复制”,不强调状态共享。 |
| 工厂模式 | 负责对象创建,不涉及对象复用,享元模式中的工厂是特殊的“缓存工厂”。 |
六、实践建议
- 明确状态划分享元模式的关键。就是:清晰区分内部状态(不变、可共享)和外部状态(可变、不可共享),这
- 工厂池设计:享元工厂应高效管理对象池(如用
hash map存储),确保快速查找和复用。 - 外部状态处理:外部状态由客户端管理,避免享元对象存储外部状态导致无法共享。
- 线程安全:多线程环境下,需为享元工厂的
get方法添加同步机制,避免并发创建重复对象。
享元模式的核心价值在于“通过共享减少对象数量处理“大量细粒度对象”场景的最佳设计模式。就是”,当平台中存在大量相似对象且内存占用过高时,它能显著优化资源应用。其关键是合理划分内部状态和外部状态,通过工厂实现对象复用,
浙公网安备 33010602011771号