设计模式(C++)-结构型模式-享元模式
一、享元模式概述
享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用,从而减少内存使用和提高性能。
核心思想:如果程序中有大量相似对象,可以将它们共有的部分(内部状态)提取出来共享,而变化的部分(外部状态)在运行时传入。
形象比喻:字母处理器的例子
- 文档中有成千上万个字母'a'、'b'、'c'等
- 每个字母都有自己的字体,大小、颜色、位置等
- 如果为每个字母都创建完整的对象,内存占用巨大
- 享元模式:创建一个共享的字母对象(内部状态:字符代码),将字体、颜色、位置等作为外部状态输入。
要解决什么问题?
问题背景:
- 软件系统中有大量相似对象
- 这些对象造成了很大的内存开销
- 大部分对象的状态可以分为内部状态和外部状态
示例:在游戏开发中,有成千上万的树、草、士兵等对象,每个对象都有位置、方向、颜色等属性。如果为买个对象都创建完整的实例,内存会迅速耗尽。
核心概念:内部状态vs外部状态
内部状态:
- 存储在享元对象内部
- 不会随环境变化而改变
- 可以被共享
- 例如:字符代码、树的模型、士兵的类型
外部状态:
- 随环境变化而改变
- 不能被共享
- 由客户端保存,使用时传入
- 例如:字符的位置、颜色、大小;树的坐标、朝向
二、享元模式UML类图
场景:森林模拟
实现一个森林模拟系统,森林中有成千上万颗树,但树的类型(内部状态)只有几种

三、代码实现
cpp
//flyweight.h
/*
享元模式(flyWeight pattern)是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用,从而减少内存使用和提高性能。
核心思想:如果程序中有大量相似对象,可以将它们共有的部分(内部状态)提取出来共享,而变化的部分(外部状态)在运行时传入。
形象比喻:字母处理器的例子
- 文档中有成千上万个字母'a'、'b'、'c'等
- 每个字母都有自己的字体,大小、颜色、位置等
- 如果为每个字母都创建完整的对象,内存占用巨大
- 享元模式:创建一个共享的字母对象(内部状态:字符代码),将字体、颜色、位置等作为外部状态输入。
*/
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
using namespace std;
//--------------------享元类:树的类型(内部状态)------------------------------
//FlyWeight
class TreeType {
private:
string name_; //内部状态
string color_; //内部状态
string texture_;//内部状态
public:
TreeType(const string&name, const string& color, const string&texture) :name_(name), color_(color), texture_(texture) {}
void draw(int x, int y)const;
string getKey()const;
};
//-------------------享元工厂------------------------------------
class TreeFactory {
private:
//存储共享的数类型对象
static unordered_map<string, shared_ptr<TreeType>>treeTypes_;
public:
static shared_ptr<TreeType> getTreeType(const string&name, const string& color, const string&texture);
static void showTreeTypes();
};
//--------------------树类(包含外部状态)-------------------------------
class Tree {
private:
int x_; //外部状态
int y_; //外部状态
shared_ptr<TreeType> type_;//共享的内部状态
public:
Tree(int x, int y, shared_ptr<TreeType> type) :x_(x), y_(y), type_(type) {}
void draw()const;
};
//------------------森林类(管理大量树)----------------------------------------
class Forest {
private:
vector<Tree> trees_;
public:
void plantTree(int x, int y, const string&name, const string& color, const string&texture);
void draw()const;
};
void testFlyWeight();
//flyweight.cc
#include "flyweight.h"
//--------------------享元类:树的类型(内部状态)------------------------------
void TreeType::draw(int x, int y)const {
cout << "在位置(" << x << ", " << y << ") 绘制一棵"
<< name_ << "树,颜色:" << color_
<< ",纹理:" << texture_ << endl;
}
string TreeType::getKey()const {
return name_ + "_" + color_ + "_" + texture_;
}
//-------------------享元工厂------------------------------------
// 静态成员初始化
unordered_map<string,shared_ptr<TreeType>> TreeFactory::treeTypes_;
shared_ptr<TreeType> TreeFactory::getTreeType(const string&name, const string& color, const string&texture) {
std::string key = name + "_" + color + "_" + texture;
auto it = treeTypes_.find(key);
if (it != treeTypes_.end()) {
// 已存在,返回共享对象
return it->second;
}
else {
// 不存在,创建新对象并存储
auto treeType = std::make_shared<TreeType>(name, color, texture);
treeTypes_[key] = treeType;
return treeType;
}
}
void TreeFactory::showTreeTypes() {
std::cout << "\n已存在的树类型(享元对象):" << std::endl;
for (const auto& pair : treeTypes_) {
std::cout << " " << pair.first << std::endl;
}
std::cout << "总共:" << treeTypes_.size() << " 种类型" << std::endl;
}
//--------------------树类(包含外部状态)-------------------------------
void Tree::draw()const {
type_->draw(x_, y_);
}
//------------------森林类(管理大量树)----------------------------------------
void Forest::plantTree(int x, int y, const string&name, const string& color, const string&texture) {
// 从工厂获取共享的树类型
auto type = TreeFactory::getTreeType(name, color, texture);
// 创建树对象,包含外部状态
trees_.emplace_back(x, y, type);
}
void Forest::draw()const {
std::cout << "\n森林中有 " << trees_.size() << " 棵树:" << std::endl;
for (const auto& tree : trees_) {
tree.draw();
}
}
void testFlyWeight() {
cout << "=================FlyWeight End===============" << endl;
Forest forest;
// 种植大量树,但只有少数几种类型
forest.plantTree(1, 1, "橡树", "绿色", "橡木纹理");
forest.plantTree(2, 3, "松树", "深绿", "松木纹理");
forest.plantTree(3, 5, "橡树", "绿色", "橡木纹理"); // 共享橡树类型
forest.plantTree(4, 7, "枫树", "红色", "枫木纹理");
forest.plantTree(5, 9, "松树", "深绿", "松木纹理"); // 共享松树类型
forest.plantTree(6, 11, "橡树", "绿色", "橡木纹理"); // 共享橡树类型
forest.plantTree(7, 13, "橡树", "黄色", "秋日橡木纹理"); // 新类型
forest.plantTree(8, 15, "松树", "深绿", "松木纹理"); // 共享松树类型
// 绘制森林
forest.draw();
// 显示共享的树类型
TreeFactory::showTreeTypes();
cout << "=================FlyWeight End===============" << endl;
}
四、优缺点总结
优点:
- 大幅减少内存使用:通过共享相似对象,避免重复粗存储相同数据
- 提高性能:减少了对象的创建和销毁开销
- 支持大量细粒度对象:可以处理远超内存容量的对象数量
- 分离内部状态和外部状态:使得程序结构更清晰、职责更分明
缺点:
- 增加系统复杂性:需要区分内部装和外部状态,增加了设计和实现的复杂度
- 可能增加运行时间:需要从挖我不传递状态,可能增加方法调用的开销
- 线程安全问题:共享的享元对象可能需要考虑线程安全问题
- 不适用于所有场景:如果对象之间差异很大,享元模式反而会增加复杂度。