1. 概述
享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。
flyweight是一个共享对象,它可以同时在多个场景中使用,并且在每个场景中flyweight都可以作为一个独立的对象——这一点与非共享对象的实例化没有区别。flyweight不能对它所运行的场景做出任何假设,这里的关键概念是
内部状态和
外部状态之间的区别。内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。而外部状态取决于Flyweight场景,并根据场景二变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给Flyweight。
Flyweight模式对那些通常因为数量太大而难以用对象来表示的概念或实体进行建模。
在某些情况下,对象的数量可能会太多,从而导致了运行时的资源与性能损耗。那么我们如何去避免大量细粒度的对象,同时又不影响客户程序,是一个值得去思考的问题,享元模式,可以运用共享技术有效地支持大量细粒度的对象。
2. 结构图
下面是享元模式的结构图:
- Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。
- ConcreteFlyweight:实现Flyweight接口,并为内部状态(如果有的话)增加存储空间。ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景。
- UnsharedConcreteFlyweight:并 非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。
- FlyweightFactory:创建并管理flyweight对象。确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果 不存在的话)。
- Client:维持一个对Flyweight的引用。计算或存储一个(多个)flyweight的外部状态。
flyweight执行时所需要的状态必定是内部或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由Client对象存储或计算。当用户调用flyweight对象的操作时,将该状态传递给它。
用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,这可以保证对它们适当地进行共享。
3. 实例分析
玩过棋牌的休闲游戏(例如:QQ游戏中的围棋,五子棋等)都知道,像五子棋,围棋等都有大量的棋子对象。对于一个游戏服务器来说,必须能容纳超大量的在线玩家,就以围棋来说,一个房间能容纳2个玩家进行对局,理论上一盘棋最多可以放361个棋子,如果我们一常规的面向对象编程进行处理,每盘棋可能有两三百个对象产生,那么一台服务器就很难支持更多的玩家,毕竟内存空间还是有限的。
这时候,我们就可以考虑使用享元模式来处理棋子,那么棋子对象可以减少到只有2个实例。再来考虑棋子对象的内部状态和外部状态。就以围棋为例,围棋只有黑白两色,我们认为这是棋子对象的内部状态;而棋子对象不同之处在于在棋盘上的位置不同,因此方位坐标就是棋子对象的外部状态了。
注:这边的例子并没有体现UnsharedConcreteFlyweight。
/*********************************************************************************
*Copyright(C),Your Company
*FileName: include.h
*Author: Huangjh
*Version:
*Date: 2017-11-20
*Description: 公共的头文件
*Others:
**********************************************************************************/
#ifndef _INCLUDE_H
#define _INCLUDE_H
typedef struct __point
{
unsigned int uiX;
unsigned int uiY;
}PiecePoint;
typedef enum {
Black,
White,
Undefined
}PieceColor;
#endif //#ifndef _INCLUDE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: Piece.h
*Author: Huangjh
*Version:
*Date: 2017-11-20
*Description: 相当于结构图中的Flyweight角色——棋子对象的超类
*Others:
**********************************************************************************/
#ifndef _PIECE_H
#define _PIECE_H
#include "include.h"
class CPiece
{
public:
CPiece(){ }
virtual ~CPiece(){ }
virtual void place(PiecePoint &Point) = 0;
};
#endif //#ifndef _PIECE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: ConcretePiece.h
*Author: Huangjh
*Version:
*Date: 2017-11-20
*Description: 相当于结构图中的ConcreteFlyweight角色——享元对象(黑棋和白棋)
*Others:
**********************************************************************************/
#ifndef _CONCRETE_PIECE_H
#define _CONCRETE_PIECE_H
#include <iostream>
#include <string>
#include "Piece.h"
class CBlackPiece : public CPiece
{
public:
CBlackPiece(PieceColor color)
:m_ePieceColor(color)
{
}
void place(PiecePoint &Point)
{
std::string strColor;
if (m_ePieceColor == Black)
strColor = "黑色";
else if (m_ePieceColor == White)
strColor = "白色";
else
strColor = "未定义";
std::cout << "在方位(" << Point.uiX << ", " << Point.uiY << ")放置一枚" << strColor << "棋子" << std::endl;
}
private:
PieceColor m_ePieceColor;
};
class CWhitePiece : public CPiece
{
public:
CWhitePiece(PieceColor color)
:m_ePieceColor(color)
{
}
void place(PiecePoint &Point)
{
std::string strColor;
if (m_ePieceColor == Black)
strColor = "黑色";
else if (m_ePieceColor == White)
strColor = "白色";
else
strColor = "未定义";
std::cout << "在方位(" << Point.uiX << ", " << Point.uiY << ")放置一枚" << strColor << "棋子" << std::endl;
}
private:
PieceColor m_ePieceColor;
};
#endif //#ifndef _CONCRETE_PIECE_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: PieceFactory.h
*Author: Huangjh
*Version:
*Date: 2017-11-20
*Description: 相当于结构图中的FlyweightFactory角色——享元工厂
*Others:
**********************************************************************************/
#ifndef _PIECE_FACTORY_H
#define _PIECE_FACTORY_H
#include <map>
#include <new>
#include "ConcretePiece.h"
class CPieceFactory
{
public:
CPiece *GetPiece(PieceColor color)
{
CPiece *pPiece = NULL;
MapPiece::iterator iter = m_mapPiece.find(color);
if (iter == m_mapPiece.end())
{
try
{
if (color == Black)
pPiece = new CBlackPiece(color);
else if (color == White)
pPiece = new CWhitePiece(color);
else
return NULL;
}
catch (std::bad_alloc &ex)
{
std::cout << "Throw bad_alloc : " << ex.what() << std::endl;
abort();
}
m_mapPiece[color] = pPiece;
return pPiece;
}
return iter->second;
}
int GetPieceNumber(void)
{
return m_mapPiece.size();
}
private:
typedef std::map<PieceColor, CPiece *> MapPiece;
MapPiece m_mapPiece;
};
#endif //#ifndef _PIECE_FACTORY_H
/*********************************************************************************
*Copyright(C),Your Company
*FileName: main.cpp
*Author: Huangjh
*Version:
*Date: 2017-11-20
*Description: 享元模式的测试用例,Client
*Others:
**********************************************************************************/
#include "PieceFactory.h"
#include "Piece.h"
#include "include.h"
int main(void)
{
PiecePoint BlackPoint1 = { 10, 20 };
PiecePoint BlackPoint2 = { 15, 40 };
PiecePoint WhitePoint1 = { 41, 28 };
PiecePoint WhitePoint2 = { 100, 20 };
CPieceFactory *pPieceFactory = new CPieceFactory();
//黑方第一枚棋子
CPiece *pBlackPiece1 = pPieceFactory->GetPiece(Black);
pBlackPiece1->place(BlackPoint1);
//白方第一枚棋子
CPiece *pWhitePiece1 = pPieceFactory->GetPiece(White);
pWhitePiece1->place(WhitePoint1);
//黑方第二枚棋子
CPiece *pBlackPiece2 = pPieceFactory->GetPiece(Black);
pBlackPiece1->place(BlackPoint2);
//白方第二枚棋子
CPiece *pWhitePiece2 = pPieceFactory->GetPiece(White);
pWhitePiece2->place(WhitePoint2);
std::cout << "目前两人对局中的棋子对象个数:" << pPieceFactory->GetPieceNumber() << std::endl;
return 0;
}
运行结果如下所示:
在方位(10, 20)放置一枚黑色棋子
在方位(41, 28)放置一枚白色棋子
在方位(15, 40)放置一枚黑色棋子
在方位(100, 20)放置一枚白色棋子
目前两人对局中的棋子对象个数:2
4. 优缺点
使用Flyweight模式时,传输、查找和/或外部状态都会产生运行时的开销,尤其当flyweight原先被存储为内部状态时。然而,空间上的节省抵消了这些开销。共享的flyweight越多,空间节约也就越大。另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
5. 适用性
Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它,当以下情况都成立时使用Flyweight模式:
- 一个应用程序使用了大量的对象。
- 完全由于使用了大量的对象,造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组合对象。
- 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
6. 参考资料
- 《大话设计模式》
- 《设计模式——可复用的面向对象软件的基础》