文章目录
享元模式
当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。享元模式(Flyweight Pattern)是一种解决方案,它通过共享技术实现相同或相似的细粒度的对象复用,从而节约内存空间,提高系统性能。
享元是一种通过与其他类似对象共享尽可能多的数据来最小化内存使用的对象;当简单的重复表示将使用不可接受的内存量时,这是一种使用大量对象的方法。
使用共享可以有效地支持大量细粒度对象。
比如有如下一个粒子系统:

在游戏运转的时候,战斗单位不断的产生粒子,会导致内存占用持续增加,因为一个粒子里面包好了非常占用内存的精灵图片,而不巧的是每个相同类型的粒子里面使用精灵图片是相同的,导致相同的资源重复占用内存资源,为了解决这个问题可以使用享元模式来解决。
通过观察Particle对象我们可以发现颜色和精灵是在同一个粒子对象里面都是一样的,对这些不变的量我
们可以称之为 **内在状态**,对于坐标、方向、速度这些在外部进行变动的我们称之为**外在状态**。

从粒子类中抽出外在状态, 那么我们只需三个不同的对象 (子弹、 导弹和弹片) 就能表示游戏中的所有粒子。我们将这样一个仅存储内在状态的对象称为****享元。
享元与不可变性:由于享元对象可在不同的情景中使用, 你必须确保其状态不能被修改。 享元类的状态只能由构造函数的参数进行一次性初始化, 它不能对其他对象公开其设置器或公有成员变量。
享元工厂 :为了能更方便地访问各种享元, 你可以++创建一个工厂方法来管理已有享元对象的缓存池++ 。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, ++如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中++。
结构

在享元模式结构图中包含如下几个角色:
- Flyweight (享元): 类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 "内在状态"。 传递给享元方法的状态被称为 "外在状态"。
- Flyweight Factory (享元工厂): 会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。
- Context (情景): 类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。
- Client (客户端): 负责计算或存储享元的外在状态。 在客户端看来, 享元是一种可在运行时进行配置的模板对象, 具体的配置方式为向其方法中传入一些情景数据参数。
实现

cpp
// 享元基类
class FlyweightBody
{
public:
FlyweightBody(string sprite) : m_sprite(sprite) {}
virtual void move(int x, int y, int speed) = 0;
virtual void draw(int x, int y) = 0;
virtual ~FlyweightBody() {}
protected:
string m_sprite; // 精灵图片
string m_color = string("black"); // 渲染颜色
};
cpp
// 享元工厂类
class BombBodyFactory
{
public:
SharedBombBody* getSharedData(string name)
{
SharedBombBody* data = nullptr;
// 遍历容器
for (auto item : m_bodyMap)
{
if (item.first == name)
{
// 找到了
data = item.second;
cout << "正在复用 <" << name << ">..." << endl;
break;
}
}
if (data == nullptr)
{
data = new SharedBombBody(name);
cout << "正在创建 <" << name << ">..." << endl;
m_bodyMap.insert(make_pair(name, data));
}
return data;
}
~BombBodyFactory()
{
for (auto item : m_bodyMap)
{
delete item.second;
}
}
private:
map<string, SharedBombBody*> m_bodyMap;
};
cpp
// 炸弹弹体
class SharedBombBody : public FlyweightBody
{
public:
using FlyweightBody::FlyweightBody;
void move(int x, int y, int speed) override
{
cout << "炸弹以每小时" << speed << "速度飞到了("
<< x << ", " << y << ") 的位置..." << endl;
}
void draw(int x, int y) override
{
cout << "在 (" << x << ", " << y << ") 的位置重绘炸弹弹体..." << endl;
}
};
// 唯一的炸弹彩蛋
class UniqueBomb : public FlyweightBody
{
public:
using FlyweightBody::FlyweightBody;
void move(int x, int y, int speed) override
{
// 此处省略对参数 x, y, speed的处理
cout << "彩蛋在往指定位置移动, 准备爆炸发放奖励..." << endl;
}
void draw(int x, int y) override
{
cout << "在 (" << x << ", " << y << ") 的位置重绘彩蛋运动轨迹..." << endl;
}
};
// 发射炮弹
class LaunchBomb
{
public:
LaunchBomb(SharedBombBody* body) : m_bomb(body) {}
int getX()
{
return m_x;
}
int getY()
{
return m_y;
}
void setSpeed(int speed)
{
m_speed = speed;
}
int getSpeed()
{
return m_speed;
}
void move(int x, int y)
{
m_x = x;
m_y = y;
m_bomb->move(m_x, m_y, m_speed);
draw();
}
void draw()
{
m_bomb->draw(m_x, m_y);
}
private:
int m_x = 0;
int m_y = 0;
int m_speed = 100;
SharedBombBody* m_bomb = nullptr;
};
cpp
int main()
{
// 发射炮弹
BombBodyFactory* factory = new BombBodyFactory;
vector<LaunchBomb*> ballList;
vector<string> namelist = { "撒旦-1", "撒旦-1", "撒旦-2", "撒旦-2", "撒旦-2", "撒旦-3"};
for (auto name : namelist)
{
int x = 0, y = 0;
LaunchBomb* ball = new LaunchBomb(factory->getSharedData(name));
for (int i = 0; i < 3; ++i)
{
x += rand() % 100;
y += rand() % 50;
ball->move(x, y);
}
cout << "=========================" << endl;
ballList.push_back(ball);
}
// 彩蛋
UniqueBomb* unique = new UniqueBomb("大彩蛋");
LaunchBomb* bomb = new LaunchBomb(unique);
int x = 0, y = 0;
for (int i = 0; i < 3; ++i)
{
x += rand() % 100;
y += rand() % 50;
bomb->move(x, y);
}
for (auto ball : ballList)
{
delete ball;
}
delete factory;
delete unique;
delete bomb;
return 0;
}
单纯享元和复合享元
标准的享元模式中既包含可以共享的具体享元类,也包含不可以共享的非共享具体享元类。
单纯享元模式
在单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。
复合享元模式
将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共
享,但是它们可以分解成单纯享元对象,而后者则可以共享。

通过复合享元模式,可以确保复合享元类CompositeFlyweight 中所包含的每个单纯享元类ConcreteFlyweight 都具有相同的外在状态,而这些单纯享元的内在状态往往可以不同。如果希望为多个内在状态不同的享元对象设置相同的外在状态,可以考虑使用复合享元模式。
与其他模式联用
- 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用**简单工厂模式**来生成享元对象。
- 在一个系统中,通常只有唯一一个享元工厂,因此可以使用**单例模式**进行享元工厂类的设计。
- 享元模式可以结合**组合模式**形成复合享元模式,统一对多个享元对象设置外在状态。
特点
主要优点
- 如果程序中有很多相似对象, 那么你将可以节省大量内存。
- 享元模式的外在状态相对独立,而且不会影响其内在状态,从而使得享元对象可以在不同的环境中被共享。
主要缺点
- 享元模式使得系统变得复杂,需要分离出内在状态和外在状态,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
适用环境
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。