享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
基础概念:
享元池:在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。
内部状态(Intrinsic State):存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。
外部状态(Extrinsic State):随环境改变而改变的、不可以共享的状态。
所以:将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
实现原理:静态变量、虚函数、继承多态、组合关联关系。
享元模式结构较为复杂,一般结合工厂模式一起使用,在它的结构图中包含了一个享元工厂类
● Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
● ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
● UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
● FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。
理解:非共享具体享元类 和 外部状态 是全部不同的概念,没有任何关系。
外部状态 可以在具体享元类中使用,作为一个函数参数使用。
优点:
(1) 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
(2) 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
(1) 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
(2) 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
延伸:单纯享元模式和复合享元模式
单纯享元模式:
在单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。
复合享元模式
将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。
补充: 享元模式通常需要和其他模式一起联用,几种常见的联用方式如下:
(1)在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象。
(2)在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计。
(3)享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态。
例子:
c++ 代码:
是单纯享元模式。
#pragma once
#include <string>
#include <map>
using namespace std;
class CFlyweightPattern
{
};
//坐标类:外部状态类
class Coordinates
{
private:
int m_x;
int m_y;
public:
Coordinates(int x, int y) {
m_x = x;
m_y = y;
}
int getX() {
return m_x;
}
void setX(int x) {
m_x = x;
}
int getY() {
return m_y;
}
void setY(int y) {
m_y = y;
}
};
//抽象享元类
class IgoChessman
{
public:
string getColor() { return ""; }
void display(Coordinates coord) {
string strcol = getColor();
printf("棋子颜色:%s,棋子位置:%d,%d", strcol.c_str(), coord.getX(), coord.getY());
}
};
//黑色棋子类:具体享元类
class BlackIgoChessman :public IgoChessman
{
public:
string getColor() {
return "黑色";
}
};
//白色棋子类:具体享元类
class WhiteIgoChessman :public IgoChessman
{
public:
string getColor() {
return "白色";
}
};
//围棋棋子工厂类:享元工厂类,使用单例模式进行设计
class IgoChessmanFactory {
private:
map<string, IgoChessman*> ht; //使用map来存储享元对象,充当享元池
public:
IgoChessmanFactory() {
IgoChessman *black, *white;
black = new BlackIgoChessman();
ht["b"] = black;
white = new WhiteIgoChessman();
ht.insert(pair("w", white));
//ht.insert(pair<string, IgoChessman*>("w", white));
//ht.insert(map<string, IgoChessman*>::value_type("w", white));
}
virtual ~IgoChessmanFactory() {
map<string, IgoChessman*>::iterator iter;
for ( iter = ht.begin(); iter != ht.end();)
{
delete (iter->second);
iter->second = nullptr;
ht.erase(iter++); //要注意iter++ 的写法
}
ht.clear();
};
//返回享元工厂类的唯一实例
static IgoChessmanFactory *getInstance() {
static IgoChessmanFactory instance;
return &instance;
}
//通过key来获取存储在Hashtable中的享元对象
IgoChessman* getIgoChessman(string color) {
return ht[color];
}
};
void TestFlyweightPattern()
{
IgoChessman *black1, *black2, *black3, *white1, *white2;
IgoChessmanFactory *factory;
//获取享元工厂对象
factory = IgoChessmanFactory::getInstance();
//通过享元工厂获取三颗黑子
black1 = factory->getIgoChessman("b");
black2 = factory->getIgoChessman("b");
black3 = factory->getIgoChessman("b");
//printf("判断两颗黑子是否相同:%d", (black1 == black2));
char Textbuf[256];
sprintf_s(Textbuf, 256, "判断两颗黑子是否相同:%d", (black1 == black2));
char strformat[256] = { 0 };
sprintf_s(strformat, "on %s in %s in func %s on line %d.", __TIME__, __FILE__, __FUNCTION__, __LINE__);
testLog(strformat, Textbuf);
//通过享元工厂获取两颗白子
white1 = factory->getIgoChessman("w");
white2 = factory->getIgoChessman("w");
printf("判断两颗白子是否相同:%d", (white1 == white2));
//显示棋子,同时设置棋子的坐标位置
black1->display(Coordinates(1, 2));
black2->display(Coordinates(3, 4));
black3->display(Coordinates(1, 3));
white1->display(Coordinates(2, 5));
white2->display(Coordinates(2, 4));
}