什么是享元模式:
享元模式的核心思想是在使用大量细粒度的对象的时候运用共享技术共享其中相同的部分。从而避免造成在分配资源的造成巨额的额外开销。
举个例子:比如说围棋游戏,那么多黑白棋子,不是每放一颗棋子都new一个棋子,不然的话会巨额的开销,姑且不说玩的人那么多。第一次放一颗黑色棋子,那么new一个表示黑色棋子的对象,以后每次放黑色棋子都返回第一次new的对象,同样白色棋子是同一道理。
再举一个例子:例如我们书籍文章,那么多重复的文字,不是每出现一次都要独立分配内存空间,而是使用共享技术,共享已经存在文字,从而降低创建对象的次数,特别是当有大量的细粒度的对象的时候,使用这种思想再合适不过,这是享元模式的核心思想:
享元模式关键之处在于共享的是哪些内在的状态,外部状态是变化,不可共享。上面例子中提到的围棋,棋子的材质和颜色是可以共享的,他们是固定不变的,而其在棋盘上坐标位置是不可以共享,因为每一颗棋子的位置是不一样的。
总结:
意图:
运用共享技术有效地支持大量细粒度的对象。
适用性:
一个应用程序使用了大量的对象。
完全由于使用大量的对象,造成很大的存储开销。
对象的大多数状态都可变为外部状态。
如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
应用程序不依赖于对象标识。由于Flyweight 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
各大博客上有一个很好的例子,围棋,我们要设计一个围棋的游戏,围棋有黑白两种颜色,我们想一想,玩家在玩游戏的时候,有那么多的棋子,玩家每落下一个棋子的时候,我们真的要重新new的一个棋子吗,这得浪费多少内存空间,姑且不说同时在线玩围棋的人那么多。我想没有哪一家游戏公司会傻到提供这种服务器来设计这样一款游戏,那得要多少资本啊。
别急,c++设计模式中的享元模式就是为了解决这样的问题.不管多少人玩围棋,当有黑白棋子的时候不用重新new一个,每次落下棋子就直接返回第一次已经new的黑白棋子对象就可以了,因为所有的黑白棋子都是一样的,我们只需要保存他们的状态就可以了(颜色,位置等等)。若果真的没有,那就new一个好了,以后每次放一个棋子都返回第一次new的黑白棋子对象。
我们首先来模拟一下没有运用享元模式的设计的方法,然后再看如何运用享元模式来设计五子棋:
首先要考虑的是棋子棋盘的实现,可以定义一个棋子的类,成员变量包括棋子的颜色、形状、位置等信息,另外再定义一个棋盘的类,成员变量中有个容器,用于存放棋子的对象。
#include
#include
#include
using namespace std;
enum PieceColor
{
white,
black
};
class PiecePos
{
public:
int x;
int y;
PiecePos(int a, int b) :x(a), y(b){}
};
class Piece
{
public:
Piece(PieceColor color, PiecePos pos) :m_color(color), m_pos(pos){}
virtual void draw(){}
~Piece(){};
protected:
PieceColor m_color;
PiecePos m_pos;
};
class WhitePiece :public Piece
{
public:
WhitePiece(PieceColor color, PiecePos pos) :Piece(color, pos){}
void draw()
{
cout << "draw a white piece." << endl;
}
~WhitePiece(){};
};
class BlackPiece :public Piece
{
public:
BlackPiece(PieceColor color, PiecePos pos) :Piece(color, pos){}
void draw()
{
cout << "draw a white piece." << endl;
}
~BlackPiece(){};
};
class PieceBoard
{
public:
PieceBoard(string whitename, string blackname) :m_WhiteName(whitename), m_BlackName(blackname){}
void SetPiece(PieceColor color, PiecePos pos)
{
Piece *piece = NULL;
if (color == white)
{
piece = new WhitePiece(color,pos);
cout << m_WhiteName << "在位置(" << pos.x << "," << pos.y << ")";
piece->draw();
}
else
{
piece = new WhitePiece(color, pos);
cout << m_BlackName << "在位置(" << pos.x << "," << pos.y << ")";
piece->draw();
}
m_VecPiece.push_back(piece);
}
~PieceBoard()
{
vector::iterator iter = m_VecPiece.begin();
for (; iter != m_VecPiece.end(); ++iter)
{
if (*iter != NULL)
{
delete *iter;
*iter = NULL;
}
}
}
private:
vector m_VecPiece;
string m_WhiteName;
string m_BlackName;
};
int main(int argc, char*argv[])
{
PieceBoard pieceBoard("A", "B");
pieceBoard.SetPiece(white, PiecePos(4, 4));
pieceBoard.SetPiece(black, PiecePos(4, 16));
pieceBoard.SetPiece(white, PiecePos(16, 4));
pieceBoard.SetPiece(black, PiecePos(16, 16));
}
可以发现,棋盘的容器中存放了已下的棋子,而每个棋子包含棋子的所有属性。一盘棋往往需要含上百颗棋子,采用上面这种实现,占用的空间太大了。如何改进呢?用享元模式。其定义为:运用共享技术有效地支持大量细粒度的对象。
在围棋中,棋子就是大量细粒度的对象。其属性有内在的,比如颜色、形状等,也有外在的,比如在棋盘上的位置。内在的属性是可以共享的,区分在于外在属性。因此,可以这样设计,只需定义两个棋子的对象,一颗黑棋和一颗白棋,这两个对象含棋子的内在属性;棋子的外在属性,即在棋盘上的位置可以提取出来,存放在单独的容器中。相比之前的方案,现在容器中仅仅存放了位置属性,而原来则是棋子对象。显然,现在的方案大大减少了对于空间的需求。
关注PieceBoard 的容器,之前是vector
棋子的新定义,只包含内在属性:
#include
#include
#include
using namespace std;
//棋子颜色
enum PieceColor { BLACK, WHITE };
//棋子位置
struct PiecePos
{
int x;
int y;
PiecePos(int a, int b) : x(a), y(b) {}
};
//棋子定义
class Piece
{
protected:
PieceColor m_color; //颜色
public:
Piece(PieceColor color) : m_color(color) {}
~Piece() {}
virtual void Draw() {}
};
class BlackPiece : public Piece
{
public:
BlackPiece(PieceColor color) : Piece(color) {}
~BlackPiece() {}
void Draw() { cout << "绘制一颗黑棋\n"; }
};
class WhitePiece : public Piece
{
public:
WhitePiece(PieceColor color) : Piece(color) {}
~WhitePiece() {}
void Draw() { cout << "绘制一颗白棋\n"; }
};
class PieceBoard
{
private:
vector m_vecPos; //存放棋子的位置
Piece *m_blackPiece; //黑棋棋子
Piece *m_whitePiece; //白棋棋子
string m_blackName;
string m_whiteName;
public:
PieceBoard(string black, string white) : m_blackName(black), m_whiteName(white)
{
m_blackPiece = NULL;
m_whitePiece = NULL;
}
~PieceBoard() { delete m_blackPiece; delete m_whitePiece; }
void SetPiece(PieceColor color, PiecePos pos)
{
if (color == BLACK)
{
if (m_blackPiece == NULL) //只有一颗黑棋
m_blackPiece = new BlackPiece(color);
cout << m_blackName << "在位置(" << pos.x << ',' << pos.y << ")";
m_blackPiece->Draw();
}
else
{
if (m_whitePiece == NULL)
m_whitePiece = new WhitePiece(color);
cout << m_whiteName << "在位置(" << pos.x << ',' << pos.y << ")";
m_whitePiece->Draw();
}
m_vecPos.push_back(pos);
}
};
int main()
{
PieceBoard pieceBoard("A", "B");
pieceBoard.SetPiece(BLACK, PiecePos(4, 4));
pieceBoard.SetPiece(WHITE, PiecePos(4, 16));
pieceBoard.SetPiece(BLACK, PiecePos(16, 4));
pieceBoard.SetPiece(WHITE, PiecePos(16, 16));
}