14. 享元模式
一、享元模式
享元模式(Flyweight Pattern)运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,是一种对象结构型模式。
享元模式通过共享技术实现相同或相似对象的重用。在享元模式中,存储这些共享实例对象的地方称为享元池(Flyweight Pool)。享元模式以共享的方式高效地支持大量细粒度对象的重用。
享元对象能做到共享的关键是区分了 内部状态(Intrinsic State)和 外部状态(Extrinsic State)。
- 内部状态 是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。
- 外部状态 是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候,再传入享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。
正因为区分了内部状态和外部状态,可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
享元模式通常包含以下几个角色:
- 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元公共的方法,这些方法可以向外界提供对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- 具体享元角色(Concrete Flyweigth):它实现了抽象享元类、称为享元对象。在具体享元类中为内部状态提供了存储空间。通常我么你可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- 享元工厂角色(Flyweigth Factory):负责创建和管理享元对象,确保享元对象可以被系统适当地共享。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户。如果不存在的话,则创建一个新的享元对象。
- 非享元角色(Unsharable Flyweigth):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可以设计为非共享具体享元类。当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
二、C++实现享元模式
非享元角色,外部状态。
// 非享元类
class Coordinates
{
private:
int x;
int y;
public:
Coordinates(void);
Coordinates(int x, int y);
int getX(void);
void setX(int x);
int getY(void);
void setY(int y);
};
Coordinates::Coordinates(void) : x(0), y(0) {}
Coordinates::Coordinates(int x, int y) : x(x), y(y) {}
int Coordinates::getX(void)
{
return x;
}
void Coordinates::setX(int x)
{
this->x = x;
}
int Coordinates::getY(void)
{
return y;
}
void Coordinates::setY(int y)
{
this->y = y;
}
在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,外部状态则通过注入的方式添加到享元类中。
// 抽象享元类
class IgoChess
{
private:
std::string color;
public:
IgoChess(void);
IgoChess(std::string color);
void display(Coordinates coordinates);
std::string getColor(void);
void setColor(std::string color);
};
IgoChess::IgoChess(void) {}
IgoChess::IgoChess(std::string color) : color(color) {}
void IgoChess::display(Coordinates coordinates)
{
std::cout << "棋子颜色:" << color << ",棋子位置:(" << coordinates.getX() << ", " << coordinates.getY() << ")" << std::endl;
}
std::string IgoChess::getColor(void)
{
return color;
}
void IgoChess::setColor(std::string color)
{
this->color = color;
}
具体享元角色:
// 具体享元类
class BlackIgoChess : public IgoChess
{
public:
BlackIgoChess(void);
};
BlackIgoChess::BlackIgoChess(void) : IgoChess("黑棋") {}
// 具体享元类
class WhiteIgoChess : public IgoChess
{
public:
WhiteIgoChess(void);
};
WhiteIgoChess::WhiteIgoChess(void) : IgoChess("白棋") {}
在享元模式中引入了享元工厂类。享元工厂类的作用在于提供一个用于存储享元对象的享元池。当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
// 享元工厂类
class IgoChessFactory
{
private:
std::map<std::string, IgoChess *> igoChessMap;
static IgoChessFactory *instance;
public:
IgoChessFactory(void);
static IgoChessFactory *getInstance(void);
IgoChess *getIgoChess(std::string color);
};
IgoChessFactory *IgoChessFactory::instance = new IgoChessFactory();
IgoChessFactory::IgoChessFactory(void)
{
igoChessMap["黑棋"] = new BlackIgoChess();
igoChessMap["白棋"] = new WhiteIgoChess();
}
IgoChessFactory *IgoChessFactory::getInstance(void)
{
return instance;
}
IgoChess *IgoChessFactory::getIgoChess(std::string color)
{
return igoChessMap[color];
}
main() 函数:
#include <iostream>
#include <map>
int main(void)
{
IgoChessFactory *factory = IgoChessFactory::getInstance();
IgoChess *blackIgoChess1 = factory->getIgoChess("黑棋");
IgoChess *blackIgoChess2 = factory->getIgoChess("黑棋");
IgoChess *blackIgoChess3 = factory->getIgoChess("黑棋");
std::cout << "blackIgoChess1 == blackIgoChess2 ? " << (blackIgoChess1 == blackIgoChess2) << std::endl;
IgoChess *whiteIgoChess1 = factory->getIgoChess("白棋");
IgoChess *whiteIgoChess2 = factory->getIgoChess("白棋");
std::cout << "whiteIgoChess1 == whiteIgoChess2 ? " << (whiteIgoChess1 == whiteIgoChess2) << std::endl;
blackIgoChess1->display(Coordinates(1, 1));
blackIgoChess1->display(Coordinates(2, 2));
blackIgoChess1->display(Coordinates(3, 3));
blackIgoChess1->display(Coordinates(1, 2));
blackIgoChess1->display(Coordinates(3, 4));
return 0;
}
三、单纯享元模式与复合享元模式
标准的享元模式结构图中既包含可以共享的具体享元类,也包含不可以共享的非共享具体享元类。但是在实际使用过程中,有时候会用到两种特殊的享元模式:单纯享元模式 和 复合享元模式。
- 在 单纯享元模式 中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。
- 将一些单纯享元对象使用组合模式加以组合,还可以形成 复合享元对象。这样的复合享元对象本身不能共享,但是它们可以包括单纯享元对象,而后者则可以共享。
通过复合享元模式,可以确保复合享元类中所包含的每个单纯享元类都具有相同的外部状态,而这些单纯享元的内部状态往往可以不同。如果希望为多个内部状态不同的享元对象设置相同的外部状态,可以考虑使用复合享元模式。
四、享元模式的总结
当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案。它通过共享技术实现相同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。
4.1、享元模式的优点
- 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
4.2、享元模式的缺点
- 享元模式需要分离出内部状态和外部状态,从而使得系统变得复杂,这使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。
4.3、享元模式的适用场景
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源。因此,在需要多次重复使用同一享元对象时才值得使用享元模式。