设计模式学习笔记--享元模式

一.简介


今天来学习一下享元模式,英文名字叫flyweight pattern,字面上看是轻量级的意思,还是感觉中文翻译比较好理解。所谓享元,就是共享相同部分的意思。当我们在设计一个游戏的时候,比如我们要在场景中绘制三个相同的人物模型,如果我们直接创建三个一样的人物模型对象并绘制,那么我们就需要每次都读入人物模型相关的网格信息+贴图材质信息,很明显,这些东西只要有一份就可以了,而唯一不同的就是他们的位置不同。那么,我们完全可以只留存一份人物模型对象,每次绘制的时候,设置一下人物的位置信息,然后绘制,也可以达到一样的效果。而这种思想,就是享元模式的思想。
下面我们来看一下享元模式的定义以及UML类图:
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
设计模式学习笔记--享元模式_第1张图片
享元模式包含了一些其他的设计模式,比如简单工厂模式和单例模式。一般地,我们会创建一个享元工厂,这个工厂一般是单例模式的。工厂内根据一定标识存储了享元对象,当我们请求一个享元对象时,工厂先在存储的对象中根据标识寻找是否有该对象,如果有,直接返回存在的对象,如果没有才重新创建一个对象然后存入工厂容器中,也就是所谓的享元池。所以,同种类型的对象在系统中只会存在一份。而享元对象可以接受和处理外部状态,比如上面我们提到的人物坐标就是外部状态,他们对于每个实例都是不同的。

二.享元模式的例子


下面,我们就通过一个例子来看一下享元模式的使用。就拿上面我们提到过的游戏人物的例子来说,我们需要在一个场景中绘制三个相同的人物,人物的坐标不同,其他都是相同的。

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 
using namespace std;

//基类:flyWeight
class BaseCharacter
{
public:
	virtual void Render(int x, int y, int z) = 0;
};

//具体类,ConcreateFlyweight
class Character : public BaseCharacter
{
public:
	void Render(int x, int y, int z) override
	{
		cout << "绘制人物, 位置:" << std::to_string(x) + std::to_string(y) + std::to_string(z) << endl;
	}

};

//工厂类,FlyWeightFactory
class CharacterFactory
{
private:
	map charMap;
public:
	BaseCharacter* GetCharacter(string name)
	{
		map::iterator it = charMap.find(name);
		if(it == charMap.end())
		{
			BaseCharacter* c = new Character();
			charMap.insert(make_pair(name, c));
			return c;
		}
		return it->second;
	}
};



int _tmain(int argc, _TCHAR* argv[])
{
	CharacterFactory* factory = new CharacterFactory();
	BaseCharacter* char1 = factory->GetCharacter("char");
	BaseCharacter* char2 = factory->GetCharacter("char");
	BaseCharacter* char3 = factory->GetCharacter("char");

	char1->Render(1,1,1);
	char2->Render(2,2,2);
	char3->Render(3,3,3);

	cout << "char1地址" << char1 << endl;
	cout << "char2地址" << char2 << endl;
	cout << "char3地址" << char3 << endl;

	system("pause");
	

	return 0;
}
结果:
绘制人物, 位置:111
绘制人物, 位置:222
绘制人物, 位置:333
char1地址00CED4A8
char2地址00CED4A8
char3地址00CED4A8
请按任意键继续. . .

通过上面的例子,我们看到,使用了享元模式之后,虽然我们也有三个对象指针,但是这三个对象指针指向的是同一个对象,他们的内存地址是相同的,换句话说,它们只是一个内存对象的实例。我们在绘制的时候,直接传入每个对象不同的位置,完成对象的绘制,效果与上面三个真正对象的效果相同,但是大大节约了内存空间与重复操作。

三.内部状态与外部状态


享元模式中有两个概念,一个叫做内部状态,一个叫做外部状态,我们分别来看一下这两个概念。

内部状态:
内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。比如上面的例子,我们人物对象的模型资源,贴图等就是内部状态,这些资源对于同一个对象的不同实例都是一致的,也就是我们享元模式所共享的东东。

外部状态:
外部状态是随环境改变而改变的、不可以共享的状态。比如上面的例子中,人物对象的位置就是外部状态,他们对于每个不同的实例来说都是不同的,而外部状态一般都有客户端来保存,客户端调用时将外部状态传入享元对象中,进行相应的操作。

四.享元模式的总结


最后,我们来看一下享元模式的优点,缺点以及使用时机。

优点:
享元模式可以将不同对象中相同部分提取作为内部状态,保存一份,供所有对象实例共享。大大节约了系统资源,减少不必要的操作。

缺点:
需要将部分属性外部化,破坏了封装性,使系统变得复杂,开销变大。

使用时机:
当我们的系统中存在很多对象,而对象中有一些状态是不变的,这些不变的状态可能会消耗很大的系统资源的时候,我们就可以考虑将这一部分作为内部状态来使用享元模式设计系统。


你可能感兴趣的:(设计模式,设计模式学习笔记,面向对象,C++,设计模式,享元模式,flyweight)