重温设计模式--6、享元模式
文章目录
- 享元模式(Flyweight Pattern)概述
- 享元模式的结构
- C++ 代码示例1
- 应用场景
- C++示例代码2
享元模式(Flyweight Pattern)概述
-
定义:
运用共享技术有效地支持大量细粒度的对象。
享元模式是一种结构型设计模式,它主要用于减少创建对象的数量,以减少内存占用和提高性能。该模式通过共享尽可能多的对象数据来实现这一点,对于那些在系统中会大量重复出现且内部状态可以被共享的对象,将其可共享的部分提取出来,进行统一管理和共享使用,而把那些依赖于具体场景不能共享的部分作为外部状态传入。 -
作用:
- 节省内存:在一些应用场景中,比如游戏开发里有大量相似的游戏元素(如相同外观的小兵角色等),如果每个都创建独立的对象,会消耗大量内存。通过享元模式共享这些元素的通用部分,能大大减少内存开销。
- 提升性能:创建对象是有一定开销的,当大量重复创建相似对象时,会影响系统性能。享元模式避免了不必要的重复创建,使得系统在运行时更加高效,例如在图形绘制系统中,频繁绘制相同图形时,共享图形对象能加快绘制速度。
- 便于管理对象状态:将对象的状态分为内部状态(可共享的、不随外部环境变化的部分)和外部状态(随具体场景变化的部分),使得对象状态的管理更加清晰和有条理,便于对不同场景下对象的行为进行控制。
享元模式的结构
-
抽象享元(Flyweight):
这是所有具体享元类的抽象,定义了享元对象的公共接口,通常包含可以接收外部状态并执行相应操作的方法等。 -
具体享元(Concrete Flyweight):
实现了抽象享元的接口,它持有内部状态,并且这些内部状态在不同实例间是可以共享的。具体享元对象的内部状态决定了它的固有行为,在创建后通常不会改变(除非有特定的修改逻辑设计)。 -
享元工厂(Flyweight Factory):
负责创建和管理享元对象。它维护一个享元对象的池(通常可以用std::map
等容器来实现),在客户端请求创建享元对象时,先检查池中是否已经存在,如果存在就直接返回已有的对象,避免重复创建;如果不存在,则创建一个新的具体享元对象并放入池中,然后返回给客户端。 -
客户端(Client):
客户端是使用享元对象的地方,它需要维护享元对象的外部状态,并且通过享元对象的接口,传入外部状态来调用相应的操作,完成业务逻辑。
C++ 代码示例1
以下以一个简单的图形绘制系统为例,假设有多种颜色的圆形需要绘制,颜色是可以共享的内部状态(因为可能有多个圆形都是同一种颜色),而圆形的坐标位置是外部状态(每个圆形在不同位置绘制)。
#include <iostream>
#include <map>
#include <string>
// 抽象享元类
class Shape
{
public:
virtual void draw(int x, int y) = 0;
virtual ~Shape() {}
};
// 具体享元类 - 圆形
class Circle : public Shape
{
private:
std::string color;
public:
Circle(std::string col) : color(col) {}
void draw(int x, int y) override
{
std::cout << "在坐标(" << x << ", " << y << ")绘制了一个" << color << "的圆形" << std::endl;
}
};
// 享元工厂类
class ShapeFactory
{
private:
std::map<std::string, Shape*> shapeMap;
public:
Shape* getShape(std::string color)
{
if (shapeMap.find(color) == shapeMap.end())
{
shapeMap[color] = new Circle(color);
}
return shapeMap[color];
}
~ShapeFactory()
{
for (auto it = shapeMap.begin(); it!= shapeMap.end(); ++it)
{
delete it->second;
}
}
};
// 客户端代码
int main()
{
ShapeFactory factory;
Shape* circle1 = factory.getShape("红色");
Shape* circle2 = factory.getShape("红色");
Shape* circle3 = factory.getShape("蓝色");
circle1->draw(10, 20);
circle2->draw(30, 40);
circle3->draw(50, 60);
return 0;
}
在上述代码中:
-
Shape
是抽象享元类,定义了draw
方法作为所有图形绘制的统一接口,这里只考虑简单的绘制操作示例,传入坐标参数来表示绘制的位置。 -
Circle
是具体享元类,它有一个表示颜色的私有成员变量color
作为内部状态,实现了draw
方法,根据传入的坐标在相应位置绘制指定颜色的圆形。 -
ShapeFactory
是享元工厂类,内部用std::map
容器维护了一个形状对象的映射表,getShape
方法根据传入的颜色参数来查找是否已经创建过对应的圆形对象,如果没有则创建一个新的Circle
对象并放入映射表中,然后返回对应的形状对象,这样就实现了相同颜色的圆形对象的共享,避免重复创建。在其析构函数中,对创建的所有形状对象进行内存释放,防止内存泄漏。 -
在
main
函数作为客户端代码部分,首先创建了享元工厂对象,然后通过工厂获取不同颜色的圆形对象(这里获取了两个红色的圆形和一个蓝色的圆形,其中两个红色圆形其实是共享同一个对象实例),最后分别传入不同的坐标调用draw
方法来模拟绘制圆形的操作,展示了享元模式如何在图形绘制场景中通过共享对象来节省内存和管理相似对象的情况。
这个代码示例在VS2010环境下可以正常编译运行,实现了享元模式的基本功能应用,你可以根据实际需求进一步扩展和完善,比如添加更多的图形种类、更复杂的状态管理等内容。
应用场景
- 游戏开发:
- 游戏中存在大量重复的游戏元素,比如相同外观的怪物、建筑等。以怪物为例,它们可能具有相同的外观(纹理、模型等可共享的内部状态),只是在游戏地图中的位置(外部状态)不同。通过享元模式,可以让多个相同外观的怪物共享同一个代表外观的对象实例,减少内存占用,同时方便对怪物外观等共享属性进行统一管理和修改。
- 游戏道具也是如此,很多道具可能有相同的基础样式,但使用时的位置、状态等不同,利用享元模式能优化道具对象的创建和管理。
- 图形绘制与可视化系统:
- 在绘制复杂图形界面或者可视化数据展示时,会有大量重复的图形元素,比如绘制地图上的多个相同图标(如代表医院、学校等的图标),这些图标本身的样式(颜色、形状等内部状态)可以共享,只是绘制的坐标位置(外部状态)不一样。享元模式可确保相同样式的图标只创建一次,提升绘制效率并节省内存,方便对图标样式进行统一更新维护。
- 对于绘制统计图表中的相同类型的图形标记(如柱状图中的柱子样式等),也可以应用享元模式进行优化。
- 文本处理与编辑系统:
- 在文字处理软件中,对于字体样式对象,字体的名称、字号、加粗、倾斜等属性是固定的(内部状态),可以共享。而每个文字在文档中的具体位置(外部状态)不同。通过享元模式管理字体样式对象,能避免为每个文字都创建重复的字体样式实例,提高内存利用效率,并且方便对字体样式进行整体的修改和统一管理,比如改变文档中所有某字号字体的颜色等操作。
- 同样,对于文档中的一些固定格式的段落样式等也可以采用享元模式来优化对象管理,减少内存开销。
- 网站开发中的资源管理:
- 网页上经常会有很多重复的图片、图标等资源展示,比如电商网站商品列表中相同类别的商品图片(可能只是商品编号等不同,但图片外观一样),可以把图片资源作为享元对象进行管理,共享相同的图片实例,减少浏览器内存占用,加快网页加载速度,同时便于对图片资源进行更新替换等操作。
- 网站的一些通用样式组件(如按钮样式、导航栏样式等)也可利用享元模式,将样式相关的内部状态共享,根据页面不同位置等外部状态进行展示和交互,优化网站前端资源的管理和性能。
C++示例代码2
#include<iostream>
#include<list>
#include<string>
#include<vector>
using namespace std;
enum COLOR{RED , BLACK};
//记录名字和坐标
typedef struct NODE
{
int x;
int y;
string name;
}NODE;
class PIECE
{
private:
COLOR m_color;
public:
PIECE(COLOR p_color):m_color(p_color){}
~PIECE(){}
virtual void DRAW(){}
};
class REDPIECE:public PIECE
{
public:
REDPIECE(COLOR p_color):PIECE(p_color){}
virtual void DRAW(){cout<<"绘制一颗红棋子"<<endl;}
};
class BLACKPIECE:public PIECE
{
public:
BLACKPIECE(COLOR p_color):PIECE(p_color){}
virtual void DRAW(){cout<<"绘制一颗黑棋子"<<endl;}
};
//棋盘
class BOARD
{
private:
list<NODE> m_ls;
PIECE*m_redpiece;
PIECE*m_blackpiece;
public:
BOARD()
{
m_redpiece=NULL;
m_blackpiece=NULL;
}
~BOARD()
{
delete m_blackpiece;
delete m_redpiece;
}
void SetPiece(COLOR p_color , NODE p_node)
{
if(p_color==RED)
{
if(m_redpiece == NULL) //有点单例模式的意思
{
m_redpiece = new REDPIECE(p_color);
}
cout<<p_node.name<<"在位置("<<p_node.x<<','<<p_node.y<<")";
m_redpiece->DRAW();
}
else
{
if(m_redpiece == NULL) //有点单例模式的意思
{
m_redpiece = new REDPIECE(p_color);
}
cout<<p_node.name<<"在位置("<<p_node.x<<','<<p_node.y<<")";
m_redpiece->DRAW();
}
m_ls.push_back(p_node);//这里只存放NODE的信息 ,不用存对象的信息,将会大大减少存储空间的消耗
}
};
int main()
{
BOARD *m_board= new BOARD();
NODE p1;
p1.name = "马";
p1.x=1;
p1.y = 2;
NODE p2;
p2.name = "车";
p2.x=1;
p2.y = 2;
REDPIECE *m_red = new REDPIECE(RED);
m_board->SetPiece(RED , p1);
BLACKPIECE *m_black = new BLACKPIECE(BLACK);
m_board->SetPiece(BLACK , p2);
return 0;
}