前言
本周五参加了Flyweitht(享元)模式研讨会,本文的目的是对这个模式的总结和分享,希望对需要学习这个设计模式的同学有所帮助。本文一共分为两个部分,第一个部分是享元模式的基础知识总结;第二个部分是享元模式的一个例子分享。一、享元模式总结
1、模式意图
运用共享技术有效地支持大量细粒度的对象。2、参与者
Flyweight--描述一个接口,通过这个接口flyweight可以接受并作用于外部状态;
ConcreteFlyweitht
-- 实现flyweitht接口,并为内部状态增加存储空间;
-- Concrete-flyweight对象必须是可共享的,它所存储的状态必须是内部的,它独立于concrete-flyweight对象的场景;
UnsharedConcreteFlyweight
--非共享对象,Flyweight接口使共享成为可能,但不强制共享;
FlyweightFactory
-- 创建并管理flyweight对象; -- 确保合理的共享flyweight。 -- 当用户请求一个flyweight时,FlyweightFactory对象提供一个已创建的实例,如果不存在,则创建一个新实例并返回;
Client
--维持一个对flyweight的引用;计算或存储flyweight的外部状态;
3、结构及效果
结构图: 效果:1、使用flyweight模式时,传输、查找、计算外部状态都会产生运行时开销,尤其是当flyweight原先被存储为内部状态时。
2、由于采用共享方式存储对象,节省存储空间,共享的flyweight越多,节约的空间也越多。
二、享元模式应用示例
1、用例背景描述
享元模式经典的应用场景之一就是文本编辑器的字体。我们在编辑文本的时候,首先需要选用一种字体,然后设置字体字号。对于同一种字体,都会有非常多细分的字体字号。如下图所示: 如果每一种具体的字号都使用一个单独的对象存储,那么,我们在编辑长篇文章时(例如我们编辑一本书),我们电脑的内存将会消耗巨大,电脑配置低的电脑甚至会出现卡顿等现象,严重影响工作心情和工作效率。这时候,享元模式模式的优势就体现得淋漓尽致了。使用享元,一种字体只有一个对象,字号作为享元对象的外部状态,当绘制字符时,把字体信息传递给享元对象即可把字符绘制出来,完全避免了同一种字体,不同字号需要使用独立对象存储的缺陷。2、用例结构图
font_factory_t:字体工厂,所有的字体由工厂创建; font_t:抽象字体类; song_font_t:具体的共享字体类,表示宋体; black_font_t:具体的共享字体类,表示黑体; client:客户,使用工厂对象创建字体及使用字体3、类说明
1、font_t:定义抽象绘制接口,接收外部状态并绘图:class font_t {
public:
font_t(font_type_t type);
font_type_t get_type();//获取字体类型
virtual draw(ctx_t* ctx, uint32_t font_size);//上下文信息及字号
private:
font_type_t font_type;
};
2、song_font_t:实现宋体类型的字体,实现宋体的绘制:class song_font_t : public font_t {
public:
song_font_t(font_type_t type);
virtual draw(ctx_t* ctx, uint32_t font_size);//上下文信息及字号
};
3、black_font_t:实现黑体类型的字体,实现黑体的绘制。class black_font_t : public font_t {
public:
black_font_t(font_type_t type);
virtual draw(ctx_t* ctx, uint32_t font_size);//上下文信息及字号
};
4、font_factory:字体工厂,负责所有字体的创建。class font_factory {
public:
font_t* create_font(font_type_t type);
static font_factory* get_factory();
private:
font_factory();
static font_factory* factory;
map<font_type_t, font_t*> font_pool;
};
font_factory* font_factory::factory = NULL;
//cpp
font_factory* font_factory::get_factory() {
if (factory == NULL) {
factory = new font_factory();
}
return factory;
}
font_t* font_factory::create_font(font_type_t type) {
if (type == SONG_FONT) {
if (font_pool.find(type) == font_pool.end()) {
font_pool[type] = new song_font_t(type);
}
return font_pool[type];
}
if (type == BLACK_FONT) {
if (font_pool.find(type) == font_pool.end()) {
font_pool[type] = new black_font_t(type);
}
return font_pool[type];
}
return NULL;
}
5、client:客户代码。...//其他代码,如上下文ctx...
font_factory* font_factory = font_factory::get_factory();//单例模式,获取字体工厂对象
font_t* song1 = font_factory->create_font(SONG_FONT);//会创建宋体对象
font_t* song2 = font_factory->create_font(SONG_FONT);//复用已存在宋体对象,不会重新创建
font_t* song3 = font_factory->create_font(SONG_FONT);//复用已存在宋体对象,不会重新创建
font_t* song4 = font_factory->create_font(SONG_FONT);//复用已存在宋体对象,不会重新创建
song1->draw(ctx, 8);//8号宋体
song2->draw(ctx, 20);//20号宋体
song3->draw(ctx, 60);//60号宋体
song4->draw(ctx, 96);//96号宋体
....
font_t* black1= font_factory->create_font(BLACK_FONT);//会创建黑体对象
font_t* black2= font_factory->create_font(BLACK_FONT);//复用已存在黑体对象,不会重新创建
font_t* black3= font_factory->create_font(BLACK_FONT);//复用已存在黑体对象,不会重新创建
font_t* black4 = font_factory->create_font(BLACK_FONT);//复用已存在黑体对象,不会重新创建
black1->draw(ctx, 8);//8号黑体
black2->draw(ctx, 20);//20号黑体
black3->draw(ctx, 60);//60号黑体
black4 ->draw(ctx, 96);//96号黑体
...//其他处理代码
总结
享元模式的目的是利用共享技术减少空间消耗。它把可变的部分隔离出来,作为外部状态,把不变的部分作为内部状态,这样,不变的部分就可以共享,实现减少空间消耗的目的。享元的创建由工厂统一负责,可以简化客户代码,降低客户代码与具体类之间的耦合程度,便于维护。对于享元工厂,一般采用单例模式实现,工厂类只需一个对象即可。在工厂中,需要维护一个享元对象池,每次创建函数调用时,首先查找对象是否已存在,只有对象不存在时才会创建对象。