一.简介
今天来学习一下享元模式,英文名字叫flyweight pattern,字面上看是轻量级的意思,还是感觉中文翻译比较好理解。所谓享元,就是共享相同部分的意思。当我们在设计一个游戏的时候,比如我们要在场景中绘制三个相同的人物模型,如果我们直接创建三个一样的人物模型对象并绘制,那么我们就需要每次都读入人物模型相关的网格信息+贴图材质信息,很明显,这些东西只要有一份就可以了,而唯一不同的就是他们的位置不同。那么,我们完全可以只留存一份人物模型对象,每次绘制的时候,设置一下人物的位置信息,然后绘制,也可以达到一样的效果。而这种思想,就是享元模式的思想。
下面我们来看一下享元模式的定义以及UML类图:
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
享元模式包含了一些其他的设计模式,比如简单工厂模式和单例模式。一般地,我们会创建一个享元工厂,这个工厂一般是单例模式的。工厂内根据一定标识存储了享元对象,当我们请求一个享元对象时,工厂先在存储的对象中根据标识寻找是否有该对象,如果有,直接返回存在的对象,如果没有才重新创建一个对象然后存入工厂容器中,也就是所谓的享元池。所以,同种类型的对象在系统中只会存在一份。而享元对象可以接受和处理外部状态,比如上面我们提到的人物坐标就是外部状态,他们对于每个实例都是不同的。
二.享元模式的例子
下面,我们就通过一个例子来看一下享元模式的使用。就拿上面我们提到过的游戏人物的例子来说,我们需要在一个场景中绘制三个相同的人物,人物的坐标不同,其他都是相同的。
1.没有用享元模式的情况
// Design Pattern.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include
#include
using namespace std;
class Character
{
private:
int x, y, z;
public:
void SetPos(int x, int y, int z)
{
this->x = x;
this->y = y;
this->z = z;
}
void Render()
{
cout << "绘制人物, 位置:" << std::to_string(x) + std::to_string(y) + std::to_string(z) << endl;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Character* char1 = new Character();
Character* char2 = new Character();
Character* char3 = new Character();
char1->SetPos(1, 1, 1);
char2->SetPos(2, 2, 2);
char3->SetPos(3, 3, 3);
char1->Render();
char2->Render();
char3->Render();
system("pause");
return 0;
}
结果:
绘制人物, 位置:111
绘制人物, 位置:222
绘制人物, 位置:333
请按任意键继续. . .
2.使用了享元模式的情况
// Design Pattern.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include
#include
#include
#include
结果:
绘制人物, 位置:111
绘制人物, 位置:222
绘制人物, 位置:333
char1地址00CED4A8
char2地址00CED4A8
char3地址00CED4A8
请按任意键继续. . .
通过上面的例子,我们看到,使用了享元模式之后,虽然我们也有三个对象指针,但是这三个对象指针指向的是同一个对象,他们的内存地址是相同的,换句话说,它们只是一个内存对象的实例。我们在绘制的时候,直接传入每个对象不同的位置,完成对象的绘制,效果与上面三个真正对象的效果相同,但是大大节约了内存空间与重复操作。
三.内部状态与外部状态
享元模式中有两个概念,一个叫做内部状态,一个叫做外部状态,我们分别来看一下这两个概念。
内部状态:
内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。比如上面的例子,我们人物对象的模型资源,贴图等就是内部状态,这些资源对于同一个对象的不同实例都是一致的,也就是我们享元模式所共享的东东。
外部状态:
外部状态是随环境改变而改变的、不可以共享的状态。比如上面的例子中,人物对象的位置就是外部状态,他们对于每个不同的实例来说都是不同的,而外部状态一般都有客户端来保存,客户端调用时将外部状态传入享元对象中,进行相应的操作。
四.享元模式的总结
最后,我们来看一下享元模式的优点,缺点以及使用时机。
优点:
享元模式可以将不同对象中相同部分提取作为内部状态,保存一份,供所有对象实例共享。大大节约了系统资源,减少不必要的操作。
缺点:
需要将部分属性外部化,破坏了封装性,使系统变得复杂,开销变大。
使用时机:
当我们的系统中存在很多对象,而对象中有一些状态是不变的,这些不变的状态可能会消耗很大的系统资源的时候,我们就可以考虑将这一部分作为内部状态来使用享元模式设计系统。