一. 作用
享元模式,顾名思义带有共享的意思,的确它就是用于共享对象的.
意图:运用共享技术有效地支持大量细粒度的对象。系统反复使用较少数量的对象,他们差别较小,从而达到反复利用的效果
二. UML类图
参与者:
- Flyweight(抽象享元类):通常是一个抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- ConcreteFlyweight(具体享元类):实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
- UnsharedConcreteFlyweight(非共享具体享元类):并不是所有抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类,当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- FlyweightFactory(享元工厂类):用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中
三 例子
举个围棋的例子,围棋的棋盘共有361格,即可放361个棋子。现在要实现一个围棋程 序,该怎么办呢?
首先要考虑的是棋子棋盘的实现,可以定义一个棋子的类,成员变量包括棋子的颜色、形状、位置等信息,另外再定义一个棋盘的类,成员变量中 有个容器,用于存放棋子的对象。下面给出代码表示:棋子的定义,当然棋子的属性除了颜色和位置,还有其他的,这里略去。这两个属性足以说明问题。
//棋子颜色
enum PieceColor
{
em_White,
em_Black,
};
//棋子位置
class Pos
{
public:
explicit Pos(int x, int y) :m_x(x), m_y(y) {}
int getX() { return m_x; }
int getY() { return m_y; }
private:
int m_x;
int m_y;
};
//棋子基类
class Piece
{
public:
Piece(PieceColor color, Pos pos) :m_color(color), m_Pos(pos) {}
virtual ~Piece() {}
virtual void Draw() {}
protected:
PieceColor m_color;//棋子颜色
Pos m_Pos; //棋子位置
};
//白棋
class WhitePiece :public Piece
{
public:
WhitePiece(PieceColor color, Pos pos) : Piece(color, pos) {}
void Draw() { cout << "下了一个白棋" << endl; }
};
//黑棋
class BlackPiece :public Piece
{
public:
BlackPiece(PieceColor color, Pos pos) : Piece(color, pos) {}
void Draw() { cout << "下了一个黑棋" << endl; }
};
//棋盘
class PieceBoard
{
public:
PieceBoard(string whiteName, string blackName) :m_WhiteName(whiteName), m_BlackName(blackName) { m_vecPiece.clear(); }
~PieceBoard()
{
int size = m_vecPiece.size();
for (int i = 0; i < size; i++)
delete m_vecPiece[i];
}
void setPiece(PieceColor color, Pos pos)
{
Piece * piece = NULL;
if(em_White == color)
{
piece = new WhitePiece(color, pos);
cout<< m_WhiteName << "在(" << pos.getX() << "," << pos.getY() << ")位置";
piece->Draw();
m_vecPiece.push_back(piece);
}
else
{
piece = new BlackPiece(color, pos);
cout << m_BlackName << "在(" << pos.getX() << "," << pos.getY() << ")位置";
piece->Draw();
m_vecPiece.push_back(piece);
}
}
private:
std::vector<Piece*> m_vecPiece;//保存每个棋子
string m_WhiteName;
string m_BlackName;
};
int main()
{
{
PieceBoard pB("鲁智深", "林冲");
pB.setPiece(em_Black, Pos(5, 5));
pB.setPiece(em_White, Pos(8, 5));
pB.setPiece(em_Black, Pos(5, 15));
pB.setPiece(em_White, Pos(3, 9));
}
getchar();
return 0;
}
运行结果
可以发现,棋盘的容器中存放了已下的棋子,而每个棋子包含棋子的所有属性。一盘棋往往需要含上百颗棋子,采用上面这种实现,占用的空间太大了。如何改进呢?
用享元模式。其定义为:运用共享技术有效地支持大量细粒度的对象。
在围棋中,棋子就是大量细粒度的对象。其属性有内在的,比如颜色、形
状等,也有外在的,比如在棋盘上的位置。内在的属性是可以共享的,区分在于外在属性。因此,可以这样设计,只需定义两个棋子的对象,一颗黑棋和一颗白棋, 这两个对象含棋子的内在属性;棋子的外在属性,即在棋盘上的位置可以提取出来,存放在单独的容器中。相比之前的方案,现在容器中仅仅存放了位置属性,而原 来则是棋子对象。显然,现在的方案大大减少了对于空间的需求。
enum PieceColor
{
em_White,
em_Black,
};
class Pos
{
public:
explicit Pos(int x, int y) :m_x(x), m_y(y) {}
int getX() { return m_x; }
int getY() { return m_y; }
private:
int m_x;
int m_y;
};
class Piece
{
public:
Piece(PieceColor color) :m_color(color){}
virtual ~Piece() {}
virtual void Draw() {}
protected:
PieceColor m_color;//此时只有颜色
};
class WhitePiece :public Piece
{
public:
WhitePiece(PieceColor color) : Piece(color) {}
void Draw() { cout << "下了一个白棋" << endl; }
};
class BlackPiece :public Piece
{
public:
BlackPiece(PieceColor color) : Piece(color) {}
void Draw() { cout << "下了一个黑棋" << endl; }
};
class PieceBoard
{
public:
PieceBoard(string whiteName, string blackName) :m_WhiteName(whiteName), m_BlackName(blackName)
{
m_vecWhitePiecePos.clear(); m_vecBlackPiecePos.clear();
}
~PieceBoard()
{
if (m_Wp)
delete m_Wp;
if (m_Bp)
delete m_Bp;
}
void setPiece(PieceColor color, Pos pos)
{
if (em_White == color)
{
if (!m_Wp)//白棋只有一个
m_Wp = new WhitePiece(color);
cout << m_WhiteName << "在(" << pos.getX() << "," << pos.getY() << ")位置";
m_Wp->Draw();
m_vecWhitePiecePos.push_back(pos);
}
else
{
if(!m_Bp)
m_Bp = new BlackPiece(color);
cout << m_BlackName << "在(" << pos.getX() << "," << pos.getY() << ")位置";
m_Bp->Draw();
m_vecBlackPiecePos.push_back(pos);
}
}
private:
std::vector<Pos> m_vecWhitePiecePos;//存放白棋的位置
std::vector<Pos> m_vecBlackPiecePos;//存放黑棋的位置
WhitePiece * m_Wp = NULL;//白棋
BlackPiece * m_Bp = NULL;//黑棋
string m_WhiteName;
string m_BlackName;
};
int main()
{
{
PieceBoard pB("鲁智深", "林冲");
pB.setPiece(em_Black, Pos(5, 5));
pB.setPiece(em_White, Pos(8, 5));
pB.setPiece(em_Black, Pos(5, 15));
pB.setPiece(em_White, Pos(3, 9));
}
getchar();
return 0;
}
运行结果
四. 优缺点
优点:
可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,避免频繁创建销毁兑现,从而可以节约系统资源,提高系统性能。
享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点:
享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
五. 适用场景
一个系统有大量相同或者相似的对象,造成内存的大量耗费。
对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。