享元模式(结构型)
前言
最近的Google AlphaGo大战李世石的围棋人机大战很火,虽然人类最终败北,但是正如Google执行董事施密特所说:“不管输赢,都是人类的巨大的胜利”。如果想自己设计一个围棋软件程序,你会发现,围棋中存在着大量的黑子和白子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,为了解决这个问题,可以考虑使用对象共享复用模式—享元模式来设计该围棋软件的棋子对象。
享元模式
享元模式,该模式通过共享技术实现相同或相似对象的重用。如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对象呢?享元模式正为解决这一类问题而诞生。在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以出现在一个字符串的不同地方,相同的字符对象都指向同一个实例。在享元模式中,存储这些共享实例对象的地方称为享元池。我们可以针对每一个不同的字符创建一个享元对象,将其放在享元池中,需要时再从享元池取出
享元模式以共享的方式支持大量细粒度对象的重用,享元对象使用的关键是区分对象内部状态和外部状态
内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享
外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部
区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份
意图
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。
由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式
参与者
Flyweight
描述一个接口,通过这个接口flyweight可以接受并作用于外部状态ConcreteFlyweight
实现Flyweight接口,并为内部状态(如果有的话)增加存储空间
ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的,即必须独立于ConcreteFlyweight对象的场景UnsharedConcreteFlyweight
并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享
在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点FlyweightFactory
创建并管理flyweight对象
确保合理地共享flyweight。当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)Client
维持一个对flyweight的引用
计算或存储一个或多个Flyweight的外部状态
flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于
ConcreteFlyweight
对象之中;而外部对象则由client对象存储或计算。当用户调用Flyweight
对象的操作时,将该状态传递给它
用户不应直接对ConcreteFlyweight
类进行实例化,而只能从FlyweightFactory
对象得到ConcreteFlyweight
对象,这可以保证对它们适当地进行共享
模式结构
代码实现
首先定义抽象的
Flyweight
,并提供Operate()
接口,接受外部状态:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// Flyweight抽象类
class Flyweight
{
private:
// 内部状态,也可以放在ConcreteFlyweight中
string _intraState;
protected:
Flyweight(string intraState) : _intraState(intraState) {};
public:
//操作外部状态extraState
virtual void Operate(const string& extraState) = 0;
virtual ~Flyweight() {};
public:
// 内部状态回读接口,也可以放在ConcreteFlyweight中
string getIntraState(void)
{
return _intraState;
}
};再分别定义
Flyweight
类的两个子类,共享的ConcreteFlyweight
类和非共享的UnsharedConcreteFlyweight
类,并分别实现其Operate()
接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 共享Flyweight的具体子类
class ConcreteFlyweight : public Flyweight
{
public:
ConcreteFlyweight(string intraState) : Flyweight(intraState) {};
public:
virtual void Operate(const string& extraState)
{
cout << "concrete flyweight intraState: " << getIntraState() << endl; // intra state & extra state
cout << "concrete flyweight extraState: " << extraState << endl;
}
virtual ~ConcreteFlyweight() {};
};
// 非共享Flyweight的具体子类
class UnsharedConcreteFlyweight : public Flyweight
{
public:
UnsharedConcreteFlyweight(string intraState) : Flyweight(intraState) {};
public:
virtual void Operate(const string& extraState)
{
cout << "UnsharedConcrete flyweight extraState: " << extraState << endl; // only extra state
}
virtual ~UnsharedConcreteFlyweight() {};
};定义
Flyweight
的工厂类,对于共享和非共享分别提供一个获取对象实例的接口:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// flyweight工厂类,若该对象已存在,直接返回该对象,否则新建一个对象,存入容器中,再返回
class FlyweightFactory
{
//保存内部状态的flyweight对象容器
private:
vector<Flyweight*> _vecFlyweight;
public:
FlyweightFactory() {};
// 获取可共享的flyweight,从共享pool中获取
Flyweight* getConcreteFlyweight(const string& key)
{
vector<Flyweight*>::iterator it = _vecFlyweight.begin();
for (; it != _vecFlyweight.end(); ++it)
{
if ((*it)->getIntraState() == key) //根据内部状态索引,若存在,直接返回该对象
{
return *it;
}
}
Flyweight* newFlyweight = new ConcreteFlyweight(key);
_vecFlyweight.push_back(newFlyweight);
return newFlyweight; //若不存在,则创建之并存入flyweight容器,返回该对象
}
int getConcreteFlyweightCount(void)
{
return _vecFlyweight.size();
}
// 获取不可共享的flyweight,直接创建并返回
Flyweight* getUnsharedConcreteFlyweight(const string& key)
{
return new UnsharedConcreteFlyweight(key);
}
};测试享元模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void FlyweightAbstractTest()
{
// factory
FlyweightFactory *factory = new FlyweightFactory();
//shared flyweight
Flyweight *concreteFlyweight = factory->getConcreteFlyweight("shared intra state");
Flyweight *concreteFlyweight2 = factory->getConcreteFlyweight("shared intra state"); // the same, return obj directly // extra state
const string extraState1 = "extra1";
const string extraState2 = "extra2";
concreteFlyweight->Operate(extraState1);
concreteFlyweight2->Operate(extraState2);
cout << "flyweight container size :" << factory->getConcreteFlyweightCount() << endl;
//unshared flyweight
const string extraState3 = "extra3";
Flyweight *unsharedConcreteFlyweight = factory->getUnsharedConcreteFlyweight("no shared intra state");
unsharedConcreteFlyweight->Operate(extraState3);
}
运行结果:
concrete flyweight intraState: shared intra state
concrete flyweight extraState: extra1
concrete flyweight intraState: shared intra state
concrete flyweight extraState: extra2
flyweight container size :1
UnsharedConcrete flyweight extraState: extra3
享元模式的分类
标准的享元模式结构图中既包含可以共享的具体享元类,也包含不可以共享的非共享具体享元类。但是在实际使用过程中,我们有时候会用到两种特殊的享元模式:单纯享元模式和复合享元模式。
单纯享元模式
在单纯享元模式中,所有的具体享元类都是可以共享的,不存在非共享具体享元类。模式图如:
复合享元模式
将一些单纯享元对象使用组合模式加以组合,还可以形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。模式图如:
使用场景
- 一个系统有大量相同或者相似的对象,造成内存的大量耗费
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中
- 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象
在享元模式的享元工厂类中通常提供一个静态的工厂方法用于返回享元对象,使用简单工厂模式来生成享元对象
在一个系统中,通常只有唯一一个享元工厂,因此可以使用单例模式进行享元工厂类的设计
享元模式可以结合组合模式形成复合享元模式,统一对多个享元对象设置外部状态
优缺点
- 优点
- 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能
- 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享
- 如果能发现这些实例数据除了几个参数外基本都是相同的,使用享元模式就可以大幅度地减少对象的数量
- 缺点
- 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长
享元模式具体实例
围棋程序
使用享元模式简单的实现前言说描述的围棋棋子
代码实现
首先分别定义棋子的外部属性(Position)和内部属性(Color):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 棋子位置 (extra state)
class Position
{
private:
int x;
int y;
public:
Position(int a, int b): x(a), y(b) {};
virtual ~Position() {};
int getX(void)
{
return x;
}
int getY(void)
{
return y;
}
//棋子的颜色(intra state) Key
typedef enum tag_Color
{
BLACK,
WHITE
}COLOR;定义抽象棋子类,包含内部属性(Color),和操作
DrawPiece(Position &pos)
,传入外部状态对象引用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 抽象棋子类(Abstract Flyweight)
class Piece
{
protected:
COLOR m_Color; // intra state
public:
Piece(COLOR color): m_Color(color) {}; // only color
virtual ~Piece() {};
public:
virtual COLOR GetColor() // intra state
{
return m_Color;
}
virtual void DrawPiece(Position &pos) = 0; // operate(extra state)
};分别定义具体的黑棋子和白棋子类,分别实现其操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 具体黑棋子类(Concrete Flyweight)
class BlackPiece : public Piece
{
public:
BlackPiece() : Piece(BLACK) {};
virtual ~BlackPiece() {};
public:
void DrawPiece(Position &pos)
{
cout << "Draw A Black Piece, Position is:" << "X:" << pos.getX() << " Y:" << pos.getY() << endl;
}
};
// 具体白棋子类(Concrete Flyweight)
class WhitePiece : public Piece
{
public:
WhitePiece() : Piece(WHITE) {};
virtual ~WhitePiece() {};
public:
void DrawPiece(Position &pos)
{
cout << "Draw A White Piece, Position is:" << "X:" << pos.getX() << " Y:" << pos.getY() << endl;
}
};定义棋子工厂类,来提供黑和白棋子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56// 棋子工厂类,提供生产白色、黑色棋子 (Flyweight Factory)
class PieceFactory
{
private:
// 棋子容器(享元池)
vector<Piece*> m_vecPiece;
public:
PieceFactory() {};
virtual ~PieceFactory()
{
for (vector<Piece*>::iterator it = m_vecPiece.begin();
it != m_vecPiece.end();
++it)
{
if (NULL != (*it))
{
delete *it;
*it = NULL;
}
}
}
private:
Piece* CreateAndAddPiece(COLOR color)
{
Piece *pRes;
if (WHITE == color)
{
pRes = new WhitePiece();
}
else if (BLACK == color)
{
pRes = new BlackPiece();
}
m_vecPiece.push_back(pRes);
return pRes;
}
public:
Piece* GetPiece(COLOR color)
{
// 若不为空,则判断vector容器里面是否有给定颜色的Piece
for (vector<Piece*>::iterator it = m_vecPiece.begin();
it != m_vecPiece.end();
++it)
{
if (color == (*it)->GetColor()) // 找到了给定颜色的Piece
{
return (*it);
}
}
// 若未空,则new一个Piece, 并添加Piece
return CreateAndAddPiece(color);
}
};测试棋子享元模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void FlyweightPieceTest()
{
PieceFactory *pPF = new PieceFactory();
// Player 1 get a white piece from the pieces bowl
Piece *pWP_1 = pPF->GetPiece(WHITE);
pWP_1->DrawPiece(Position(1,10)); // new a white piece, set pos = 1,10
// Player 2 get a black piece from the pieces bowl
Piece *pBP_1 = pPF->GetPiece(BLACK); // new a black piece , set pos = 1,20
pBP_1->DrawPiece(Position(1,20));
// Player 1 get a white piece from the pieces bowl
Piece *pWP_2 = pPF->GetPiece(WHITE); // get the existing white piece, but set pos = 2,10
pWP_2->DrawPiece(Position(2,10));
// Player 2 get a black piece from the pieces bowl
Piece *pBP_2 = pPF->GetPiece(BLACK); // get the existing a black piece , but set pos = 2,20
pBP_2->DrawPiece(Position(2,20));
if (NULL != pPF)
{
delete pPF; pPF = NULL;
}运行结果:
Draw A White Piece, Position is:X:1 Y:10
Draw A Black Piece, Position is:X:1 Y:20
Draw A White Piece, Position is:X:2 Y:10
Draw A Black Piece, Position is:X:2 Y:20