设计模式之享元模式(flyweight)
何谓享元
首先我们解释一下什么是享元?
这里所说的享元出自大名鼎鼎的设计模式一书,英文flyweight,中文由谁翻译成“享元”一词已不可考,但不得不说,这个翻译实在是精彩;望文即可生意,比起洋鬼子的flyweight高明太多。这不免让我等程序员被洋鬼子压倒在头上多年终于可以聊以自慰(也就是yy),虽然我不如你,但我们的老祖宗可比你们的老祖宗要强多了 – 我们的老祖宗确实很厉害,搞得我们除了老祖宗的东西也确实没啥能拿出手的,大凡老子是英雄,儿子基本上都是狗熊。这真是悲哀
扯远了,我们继续享元;那何为享元,顾名思义,共享的单元嘛。实例A和B都包含指向同一个实例C的索引,而不是直接包含C,C就是享元;数据结构描述如下
struct C {
int iIndexOfC;
/* ...*/
}stC;
struct A {
int iIndexOfA;
struct C *pstFlyWeight;
}stA = {0, &stC};
struct B {
int iIndexOfB;
struct C *pstFlyWeight;
}stB = {0, &stC};
享元的应用
可以说,无论你有没有意识到,代码中享元实际上无处不在。远的不说了,就是设计模式中的举例,文档中文字格式的存储,我们每天都用到,谁不用office软件?如果说这个还是有些遥远,毕竟不是自己设计的部分,那我们再看个例子;如果要存储一个学生信息,包括学号和毕业学校名称,有人会这样设计这个数据结构吗?
struct student {
unsigned int uiIndex;
char szCollegeName[32];
};
大部分情况来说,我们都不会这样操作,这确实很浪费存储空间;虽然现在电子产品越来越便宜,但我们也不能这样败家。我们可以这样
struct college {
unsigned short usCollegeIndex;
char szCollegeName[32];
};
struct student {
unsigned int uiIndex;
unsigned short uiCollegeIndex;
};
这样效果比较好一些
进阶篇
如果享元只是像我举的例子那样简单,享元也就不能称之为享元了;我们再仔细想想文档处理的例子,每个字符包括大小,字体...,理论上应该定义成
struct char_formated {
char cCharactor;
short sFont;
char cSize;
struct position stPosition;
/* ... */
};
struct file {
char_formated[x];
};
应用享元模式,定义成这样也就差不多了
struct format_template {
short sFormatId;
short sFont;
char cSize;
/* ... */
};
struct char_formated {
char cCharactor;
short sForamtId;
struct position stPosition;
};
当然,设计模式里面玩的更狠一些,干脆搞成这样
struct formated_char {
short sFormatId;
char cCharactor;
char cSize;
short sFont;
/* ... */
};
struct char_formated {
short sForamtId;
struct position stPosition;
};
有人会问这个问题吗?- 从表面上看,存储空间不是耗费更多了吗?至少多了short的空间吧。我看是没有人会问了
所以这才是享元的精髓,把一些私有的信息公共出来,以达到共享的效果;
文档当然是享元的一个完美应用,但平常的代码中有很多的实际的例子,只不过大家没有多想,或者没有在意
举例:公司的应届生招聘信息记录
struct student {
unsigned int uiIndex;
unsigned int uiYearEnterCollege;
unsigned int uiYearGraduate;
unsigned char ucMonthEnterCollege;
unsigned char ucMonthGradute;
unsigned short usCollegeIndex;
unsigned char ucAge;
unsigned char ucGender;
unsigned char ucMarried;
};
这个数据结构定义没有任何问题;相信碰到这种情况,大部分人随手也就这样做了;但实际上如果仔细考虑,完全可以使用享元:理论上同一届的应届生年龄相差不大(3岁差不多了吧),入学时间和毕业时间应该都相同,性别,婚否每个2,那一共有多少种情况呢?3乘2再乘2 = 12
所以我们实际上可以这样考虑下面的设计
struct template {
unsigned int uiYearEnterCollege;
unsigned int uiYearGraduate;
unsigned char ucMonthEnterCollege;
unsigned char ucMonthGradute;
unsigned char ucAge;
unsigned char ucGender;
unsigned char ucMarried;
char cTemplateId;
};
struct student {
unsigned int uiIndex;
unsigned short usCollegeIndex;
char cTemplateId;
};
从这里看到的数据,大约会节约12/20 = 60%的空间(当然实际上应该是没有的,因为学生还有很多其它的信息);但这个数据也足够振奋人心的了
但也由此可见,享元并不那么直观,我们需要细心观察和总结;
遗留了一些问题:
1 usCollegeIndex可以抽取到享元中吗?
2 什么时候需要考虑享元?
3 什么样的信息适合抽取出来作为享元?
小结
享元是一种设计模式:并不针对面对对象,在c语言,数据库等等领域都有重要的价值;
享元更是一种思路,可共享的才是最好的 – 这无疑是面对对象的根本要义;在所有的业务领域也不失为一个努力的方向
同时享元还揭示了一个真理:不完美就是一种完美,它用一个不完美的手法达到了完美的效果(感觉有点像易经中阴中有阳,阳中有阴,呵呵);如果你仔细观察,生活中这种例子数不胜数。
享元如此精彩,但又如此朦胧;这不得不要求我们睁大双眼,每日三省代码:这里可以享元吗?