第12讲:Flyweight 享元模式

2006.3.28 李建忠

面向对象的代价

面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。但是,在某些特殊的应用中,由于对象的数量太大,采用面向对象会给系统带来难以承受的内存开销。比如图形应用中的图元等对象、字处理应用中的字符对象等。

image

 

动机(Motivation)

采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。

如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?

 

意图(Intent)

运用共享技术有效地支持大量细粒度的对象。

——《设计模式》GoF

 

例说Flyweight应用

image

image

如果是对字符char使用享元模式,本来每个字符只占用2字节,而享元模式要使用指针,至少每个字符需要4字节,这样就适得其反了,反而消耗了更多的内存资源。

image

但是字体就需要用享元模式了,因为它的大小比字符大得多,在我们这个例子中有12bytes,如果是它倍乘的话数量级就很大了,很消耗内存。

其实更精确地说,对于C#,每一个类还需要有一个虚表指针和做垃圾回收控制的空间,一共占8bytes。因为C#中每一个类都是继承自object类,所以每个类都带有虚函数和类型信息。所以Font类应该是20bytes的倍乘效应。在Charactor类中,char字符占2bytes;Font类型有个指针,占4bytes;还有Font里的20bytes;还有8bytes的虚表指针和垃圾回收;因为32位机器上有一个填充的效应,char字符还需要额外占用2bytes,需要补齐;因此Charactor类共占36bytes。在客户代码中,有100000个Charactor类,那么大约在会带来3.6M的数据。如果是服务器程序,那么3M数据可以接受。但如果字符数量级再增加,那么开销是不言而喻的。

改善结构:

image

Charactor类里的CFont属性 

image

这里当我们使用Charactor的时候,我们设置它字体时,如果在它静态的Hashtable里面已经有的话,我们就把指针直接指向它,就不要再去新建一个字体对象了

image

客户代码中第61行和63行,f1和f2占用两块不同的内存,但是到了68行和73行,我们实际上在内部透明的把它们转化为让它们只用f1一块内存,f2的内存就不会再让它持有。

要注意的是,在.NET里面使用字符串的时候,它实际上已经运用了享元模式,比如我们字符串里出了20个“Hello World”,实际上在系统里面只存了1个“Hello World”,其它的都是用指针指向它。这个前提当然是字符串的代价比指针大。

 

结构(Structure)

image

这种结构其实可以仅作为参考,它并不是一个作为绝对标准化的结构。基本思路实现是这样,但是至于是否有必要放到一个静态工厂来做,还值得商榷。像我们上面的例子就直接把它放在一个属性里面去做。

 

Flyweight模式的几个要点

面向对象很好地解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。

Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。

对象的数量太大从而导致对象内存开销加大——什么样的数量才算大?这需要我们仔细的根据具体应用情况进行评估,而不能凭空臆断。

 

.NET架构中的Flyweight应用

.NET在C#中有一个Code Behind机制,它表面有一个aspx文件,背后又有一个cs文件,它的编译过程实际上会把aspx文件解析成C#文件,然后编译成dll,在这个过程中,我们在aspx中写的任何html代码都会转化为literal control,literal control是一个一般的文本控件,它就表示html标记。当这些标记有一样的时候,构建控件树的时候就会用到Flyweight模式。

它的应用并不是那么平凡,只有在效率空间确实不高的时候我们才用它。

2010.10.6

posted @ 2010-10-06 17:12  山天大畜  阅读(747)  评论(0编辑  收藏  举报