简介
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享来有效地支持大量细粒度对象的重用,减少创建对象的数量以节省系统资源,特别是内存。这种模式特别适用于那些有大量相似对象的情况。
1 解决的问题
当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等问题。例如,在一个文本字符串中存在很多重复的字符,如果每个字符都用一个单独的对象来表示,将会占用较多的内存空间。那么,如何去避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作?享元模式正为解决这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用。在逻辑上每个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象。
2 C++案例
在下面的例子中,我们将创建一个简单的字符处理程序,该程序使用享元模式来避免为文档中每个出现的字符都创建一个新对象。
2.1 定义享元接口和具体享元类
// Flyweight.h
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
// 享元接口
class Character {
public:
virtual ~Character() = default;
virtual void display() const = 0;
};
// 具体享元类
class ConcreteCharacter : public Character {
char glyph; // 字符图形
public:
ConcreteCharacter(char glyph) : glyph(glyph) {}
void display() const override {
std::cout << glyph;
}
};
2.2 实现享元工厂
享元工厂负责创建和管理享元对象,确保相同的享元实例被共享使用。
// CharacterFactory.h
#include "Flyweight.h"
using namespace std;
class CharacterFactory {
std::unordered_map<char, Character*> characterMap; // 存储享元对象
public:
~CharacterFactory() {
for (auto it : characterMap) {
delete it.second;
}
}
Character* getCharacter(char glyph) {
if (characterMap.find(glyph) == characterMap.end()) {
characterMap[glyph] = new ConcreteCharacter(glyph);
}
return characterMap[glyph];
}
void get_num(){
cout << "map num: "<< characterMap.size() << ", they are: ";
for (auto it : characterMap) {
cout<< it.first << " ";
}
cout << "\n";
}
};
2.3 使用享元模式
// main.cpp
#include "CharacterFactory.h"
int main() {
std::string document = "AAZZBBZB";
CharacterFactory factory;
for (char glyph : document) {
Character* character = factory.getCharacter(glyph);
character->display(); // 使用享元对象
}
std::cout<< std::endl;
factory.get_num();
return 0;
}
运行结果:
$ g++ main.cpp && ./a.out
AAZZBBZB
map num: 3, they are: B Z A
在这个示例中:
Character
接口定义了享元对象的通用方法display
。ConcreteCharacter
是实现了Character
接口的具体享元类,它代表文档中的一个字符。CharacterFactory
是享元工厂,负责创建和管理ConcreteCharacter
对象的实例。它使用一个映射(characterMap
)来存储已经创建的享元对象,并在请求相同字符时返回现有的享元实例而不是创建一个新实例。- 在
main
函数中,我们通过CharacterFactory
为文档中的每个字符获取享元对象并显示它们。这里,虽然文档中的字符可能多次出现,但每个字符实际上只创建了一个ConcreteCharacter
实例。
通过享元模式的使用,我们减少了创建相同字符对象的数量,有效地节约了资源。
3 优缺点
3.1 优点
享元模式的主要优点如下:(1)可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。(2)享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
3.2 缺点
享元模式的主要缺点如下:(1)享元模式需要分离出内部状态和外部状态,从而使得系统变得复杂,这使得程序的逻辑复杂化。(2)为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
3.3 适用场景
在以下情况下可以考虑使用享元模式:(1)一个系统有大量相同或者相似的对象,造成内存的大量耗费。(2)对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。(3)在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源。因此,在需要多次重复使用同一享元对象时才值得使用享元模式。
4 与其他模式的联合使用
享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
- 在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
- 在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计。
- 享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。
4.1 享元与组合联合使用
组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次关系。当你需要以统一的方式处理个别对象和组合对象时,享元模式可以与组合模式结合使用,尤其是在组合对象中包含大量共享的享元对象时,这种结合可以有效地减少内存的占用。
4.2 示例场景:图形编辑器中的图形对象
假设你正在开发一个图形编辑器,其中包括多种图形对象(如线条、圆形、方形等)。用户可以创建复杂图形,这些复杂图形可以由简单图形组合而成。为了内存效率,简单图形的某些属性(如颜色、线型)可以使用享元模式来共享。
// Flyweight: 共享的颜色属性
class Color {
public:
// 颜色的具体数据
};
class ColorFactory {
std::map<std::string, Color*> colors; // 缓存已创建的颜色对象
public:
Color* getColor(const std::string& colorName) {
if (colors.find(colorName) == colors.end()) {
colors[colorName] = new Color(/* 初始化颜色 */);
}
return colors[colorName];
}
};
// Composite: 图形对象的层次结构
class Graphic {
public:
virtual void draw() = 0; // 绘制图形
};
class CompositeGraphic : public Graphic {
std::vector<Graphic*> children;
public:
void draw() override {
for (Graphic* child : children) {
child->draw();
}
}
void add(Graphic* graphic) {
children.push_back(graphic);
}
};
class Circle : public Graphic {
Color* color; // 使用享元模式共享的颜色对象
public:
Circle(Color* color) : color(color) {}
void draw() override {
// 绘制圆形,使用共享的颜色
}
};
// 在客户端代码中使用
int main() {
ColorFactory colorFactory;
CompositeGraphic picture;
Color* red = colorFactory.getColor("Red");
Color* blue = colorFactory.getColor("Blue");
picture.add(new Circle(red));
picture.add(new Circle(blue));
picture.draw();
return 0;
}
在这个例子中,ColorFactory
用于创建和管理Color
对象的享元实例,而CompositeGraphic
则允许你将图形对象组合成复杂的结构。每个Circle
对象在被创建时都会引用共享的Color
对象,这样,即使有成百上千个图形对象,相同颜色的实例也只需要创建一次并被共享使用,从而节省内存。
通过这种方式,享元模式与组合模式的结合使用提供了一种既节省内存又能保持结构灵活性的解决方案,特别适合于处理那些包含大量重复数据的复杂对象结构。