C++享元模式
·
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用,从而减少内存消耗和提高系统性能。这种模式特别适合当系统中存在大量相似对象,且这些对象的大部分状态可以共享的场景。
享元模式的核心角色
- 享元(Flyweight):定义共享对象的接口,包含可以被多个对象共享的内部状态
- 具体享元(Concrete Flyweight):实现享元接口,存储内部状态
- 非享元(Unshared Flyweight):不参与共享的对象,通常包含随环境变化的外部状态
- 享元工厂(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):随环境变化的状态,不能被共享,由客户端保存和传递(如示例中的字符位置)
享元模式通过分离这两种状态,使相同内部状态的对象可以被共享,从而大幅减少系统中的对象数量。
享元模式的工作流程
- 享元工厂创建并管理享元对象,确保相同内部状态的对象只被创建一次
- 客户端向工厂请求享元对象,提供内部状态
- 客户端保存并管理外部状态,在使用享元对象时将外部状态传递给它
- 享元对象结合内部状态和客户端提供的外部状态完成操作
享元模式的应用场景
- 系统中存在大量相似对象,且这些对象消耗大量内存
- 对象的大部分状态可以共享(内部状态),只有少量状态是独特的(外部状态)
- 需要缓冲池的场景(如数据库连接池、线程池)
- 字符串常量池、缓存系统等
享元模式的优缺点
优点:
- 减少了系统中的对象数量,降低内存消耗
- 提高了系统性能,减少了对象创建和销毁的开销
- 通过共享对象,提高了资源利用率
缺点:
- 增加了系统复杂度,需要分离内部状态和外部状态
- 外部状态由客户端管理,可能会增加客户端的负担
- 共享对象不能有独立的生命周期,管理不当可能导致问题
享元模式在C++标准库中也有应用,例如std::string的字符串常量池,多个相同的字符串字面量会共享同一份内存;此外,某些GUI库中的按钮、图标等组件也常使用享元模式来优化性能。
更多推荐

所有评论(0)