C++设计模式_13_Flyweight享元模式
Flyweight享元模式仍然属于“对象性能”
模式。
文章目录
- 1. 动机(Motivation)
- 2. 模式定义
- 3. 结构( Structure)
- 4. 代码演示
- 5. 要点总结
- 6. 其他参考
1. 动机(Motivation)
在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价一一主要指内存需求方面的代价。
通常不用担心对象数量问题,但是存在倍乘效应的情况下就会占用很大的内存需求,常见的优化方法是共享技术,共享技术是面向对象里经常用来解决性能问题的手段,这也是Flyweight的核心思想。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
2. 模式定义
运用共享技术
有效地支持大量
细粒度的对象。
–《设计模式》GoF
比如说字符串,字符串在系统中使用的量是非常大的,占用的内存也是比较大的,常见的系统包括stl等都用了一定的共享技术,java和C#会在编译器层面用一些共享技术。除了字符串,线程也是一类例子。
3. 结构( Structure)
GoF中面向对象的一种描述方法。从上面的类图可以看到FlyweightFactory的工厂,本来创建对象是一个个new,但是这种方式会导致应该共享的却没有共享,利用享元的工厂GetFlyweight(key),用key做判断对象是否创建,如果已经创建直接返回,如果对象之前没有创建,那么先创建一个,加到对象池中,然后再返回。右下角的可以忽略UnsharedConcreteFlyweight,因为有些对象是支持共享,有些是不支持的。
4. 代码演示
以下为示意性代码,比如说要设计一个字处理系统,里面具有很多的假设,你将字体类型实现为一个对象,以下使用Font类来描述字体
class Font {
private:
//unique object key
string key;
//object state
//....
public:
Font(const string& key){
//...
}
};
字体对象的量很大,一个系统中每一个字符都有一个字体,实际上十页文章,真正使用的字体也就是四五种左右,而不是每一个字符的字体对象都不一样。如果不加区分的给每一个字符创建一个字体对象,系统将很难承受。这个时候就需要利用享元的方式实现。
字体一般能确定一个key
,比如string key;
可以load到相关的资源,利用key来构造对象。
真正推荐的Flyweight享元模式创建方式如下:
class FontFactory{
private:
map<string,Font* > fontPool;
public:
Font* GetFont(const string& key){
map<string,Font*>::iterator item=fontPool.find(key);
if(item!=footPool.end()){
return fontPool[key];
}
else{
Font* font = new Font(key);
fontPool[key]= font;
return font;
}
}
void clear(){
//...
}
};
设定一个FontFactory的字体工厂,用到map<string,Font* > fontPool;
维持一个字体对象池fontPool, Font* GetFont(const string& key)
传递一个key字符串,map<string,Font*>::iterator item=fontPool.find(key);
查找key,如果找到key,判断迭代器不等于end,直接返回,说明之前创建过,如果没有找到说明这种对象没有创建,首先进行创建Font* font = new Font(key);
,创建之后,fontPool[key]= font;
添加到对象池中,再将对象返回。
大家可以看到,同一种key永远只有一个字体对象对应,只创建了一个字体对象,不会有很多,当然Key是有限的,可能你有10万个字符,但只有10种key及对应的字体对象,这样的话只是维持了10个字体对象,也就是维持了map,每次get的时候取。
实现起来各种各样,但是核心的思想就是共享的方式,有就返回,没有就创建返回。
整体代码
class Font {
private:
//unique object key
string key;
//object state
//....
public:
Font(const string& key){
//...
}
};
ß
class FontFactory{
private:
map<string,Font* > fontPool;
public:
Font* GetFont(const string& key){
map<string,Font*>::iterator item=fontPool.find(key);
if(item!=footPool.end()){
return fontPool[key];
}
else{
Font* font = new Font(key);
fontPool[key]= font;
return font;
}
}
void clear(){
//...
}
};
5. 要点总结
-
面向对象很好地解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。
Flyweight主要解决面向对象的代价问题
,一般不触发面向对象的抽象性问题 -
Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理
需要注意,对象创建出来之后,不能更改对象状态,往往这种对象创建出来之后就是只读的,像java和c#中的字符串是只读的,而C++标准中字符串不是只读的,这是因为背后使用了prototype的设计模式解决copy and write问题,总体来讲这样的对象出去最好是只读
的,否则共享就不成立了,共享后被更改,其他对象共享的也就被更改了。
- 对象的数量太大从而导致对象内存开销加大,–什么样的数量才算大?这需要我们存细的根据具体应用情况进行评估,而不能凭空臆断
简单的说明,一个对象到底占多大,使用sizeof和根据类内的数据类型,对齐,虚函数表指针(32位为4byte)等加起来之后就是对象的size,再看对象有多少个来计算占用内存。
6. 其他参考
C++设计模式——享元模式