设计模式:Flyweight模式(享元模式)

记得在大学时,用当时的那台电脑玩魔兽,一旦视野内存在大量兵的时候,游戏就会暂时卡掉,无法进行任何操作。那就是因为系统new出来太多的兵,所需内存空间已经超出了硬件可提供的空间而导致游戏卡掉的。事实上,当遇到类似问题时,我们可以优先考虑一下Flyweight模式。

定义
GOF: 使用共享技术高效地支持大量的细粒度对象。
中文名字的意思也是很称职的:共享同一个元对象。

结构图


                                                图一:GOF中的Flyweight模式结构图

解说
*Flyweight模式的设计意图是避免大量相似对象的创建。一般会先创建一个或一组具有相同属性的元对象,使客户端可以共享使用这个元对象。由于元对象被大量的共享使用,因此对内存的占用有很大的改善。
*Flyweight对象能够做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。
*内部状态存储在Flyweight对象内部,并且不会随环境的改变而有改变。因此,一个Flyweight对象可以具有内部状态并可以共享。
*外部状态是随环境改变而改变的、不可共享的状态。Flyweight对象的外部状态必须由客户端保存,并在对象被创建后,在需要使用的时候再传给Flyweight对象。
*外部状态不可以影响Flyweight对象的内部状态。换句话说,它们是相互独立的。

示例
Design Patterns[GOF]中给的例子是一个文档编辑器的format功能的例子。英文里只有几十个标点符号和26个英文字符可用,而在一篇通常大小的文档中,一个字符基本上会用几百遍以上,只不过分别具有不同的style和位置而已。在这里,对于每一个Character对象来说,它所代表的字符是永远不会变动的,属于内部状态;而它的style和位置则是经常变动的,属于外部状态。

文章的开始,我们提到了魔兽游戏的例子。不过魔兽中的士兵是否足够多是与地图相关的,所以采用Flyweight模式是不是最好的方案还要认真斟酌。不过我们还有别的例子可用,最常见的就是棋牌游戏。
围棋桌是19X19,一个可以下361个棋子,可能放在一局游戏中不是太多。但是现在的游戏大都网络化了,一台服务器可以同时提供n局对战,那么所需要创建的棋子对象就会非常的多。而棋子所具有的属性也很简单,圆形和黑、白色就是它的内部属性,一旦创建好,就永远不会变;而它的位置就是外部属性。针对这个例子提供了一个简单的设计。


                                       图二:围棋中应用Flyweight模式的设计

在类WeiqiChessman中,shape和color都是对象的内部状态,一旦确定之后就永远不会改变的;而它所在的位置则是在客户端每次调用它的方法moveTo()时才能够得知,这是它的外部状态,是可以被改变的。
这个模式对于其他类似于的棋牌游戏来说也是适合的,只要系统的需求满足以下条件即可。

适用于
当以下所有 的条件都满足时,可以考虑使用Flyweight模式:
(1) 一个系统有大量的对象。
(2) 这些对象耗费大量的内存。
(3) 这些对象的状态中的大部分都可以外部化。
(4) 这些对象可以按照内部状态分成很多组,当把外部状态从对象中剔除时,每个组都可以仅用一个对象代替。
(5) 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
最后,使用Flyweight模式需要维护一个记录了系统已有的所有共享单元的表,而这需要耗费资源。因此,应当在有足够多的Flyweight实例可供共享时才值得使用Flyweight模式。

优缺点
★Flyweight模式的优点在于它大幅度的降低了内存中对象的数量。但是,它做到这一点所付出的代价也很高:
Flyweight模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
Flyweight模式将共享对象的状态外部化,而读取外部状态使得运行时间稍微变长,算是以时间换取了空间。

Object Pool是Flyweight模式吗?
曾经看到有些文章中是把Object Pool认为是Flyweight模式的一种,并且也有一些争论。
关于这一点,笔者也进行了一些思考和调查。个人认为 http://www.mindspring.com/~mgrand/pattern_synopses.htm 和 http://www.oodesign.com/object-pool-pattern.html 的理解更为合理。也就是把Object Pool作为23种模式之外的一种新的模式,并且把它归类为创建型模式中(Creational Patterns)。

应用Flyweight模式的关键之一是内部状态和外部状态的区分,而Object Pool所应用的场景基本上不会考虑这一点。
Flyweight模式所解决的问题之一是使大量的对象共享同一个元对象,是对空间(memory)的优化;而Object Pool主要是解决的问题是对象的创建过程很耗时,所以通过pool的方式来快速的提供对象,这是对时间(performance)的优化,当然,可能也会有空间上的考虑。
Flyweight模式中的元对象本身是Immutable的,是可以同时被多个客户端使用的,是一种并行的方案,而Object Pool中创建的对象一旦被某个客户端使用中,另外一个客户端就不能够同时使用这个对象,是一种串行的方案。

基于以上分析,我们可以看出,Object Pool模式主要是针对对象的创建过程很困难,很耗时,很耗资源,或者说对象的创建个数有限制,而提供的一种解决方案。JDBC程序中提供的连接池就是一个很好的例子,数据库连接的创建包含物理连接的创建,非常的消耗时间,另外大部分数据库产品可同时支持的数据库连接个数也是有限的。
Object Pool模式基本上不关心外部的使用,并不要求客户端持有任何其他信息。从某种程度上讲,它的结构和功能和工厂方法比较接近,只是内部的创建对象的过程不一样。

因此,笔者也认为把Object Pool归类为23个模式之外的一种新的创建型模式更为合理。

参考资料
1. Flyweight Pattern
   http://www.oodesign.com/flyweight-pattern.html
2. Overview of Design Patterns
   http://www.mindspring.com/~mgrand/pattern_synopses.htm
3. Object Pool
   http://www.oodesign.com/object-pool-pattern.html
4. 设计模式之Flyweight(享元) FlyWeight模式
   http://www.jdon.com/designpatterns/flyweight.htm
5. Flyweight模式之我见
   http://www.jdon.com/jivejdon/thread/31903
6. Flyweight模式
   http://chenchh.javaeye.com/blog/668089
7. java flyweight模式
   http://blog.csdn.net/ykdsg/archive/2010/02/24/5321750.aspx
8. Java对象池技术的原理及其实现
   http://java.chinaitlab.com/advance/731726.html
9. Java小对象的解决之道——对象池(Object Pool)的设计与应用
   http://blog.csdn.net/bryantd/archive/2006/08/20/1100019.aspx

你可能感兴趣的:(设计/UML/MDA,java)