📄 flyweightpattern.htm
字号:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<link rel="stylesheet" href="css/stdlayout.css" type="text/css">
<link rel="stylesheet" href="css/print.css" type="text/css">
<meta content="text/html; charset=gb2312" http-equiv="content-type">
<title>Flyweight 模式</title>
</head>
<body>
<h3><a href="http://caterpillar.onlyfun.net/GossipCN/index.html">From
Gossip@caterpillar</a></h3>
<h1><a href="CppGossip.html">Design Pattern: Flyweight 模式</a></h1>
在 <a href="GoF.htm">Gof 的书</a>中指出,Flyweight的目的在于运用共享技术,使得一些细粒度的物件可以共享。<br>
<br>
Flyweight在牛津字典中的解释是"boxer of the lightest class"。意思是特轻量级拳击手?其实应该是取"the
lightest
class"这部份的解释,一个特轻量级类别,这个类别所产生的物件可以共用在每一个场合(context),并依场合资讯表现物件外观。<br>
<br>
在书中所举出的例子是文档编辑器中的字元物件,若每个字元物件会包括字元、大小、字型等等不同的资讯,想想一篇文章中可能出现多少字元,如果我们为每一个
字元都使用一个物件来完整描述有关于它的讯息,那么一篇文字中将会耗用多少的记忆体?!字元本身应可以共享,而大小、字型等等不同的资讯再分别设定。<br>
<br>
考虑数量多且性质相近的物件时,将该物件的资讯分为两个部份:内部状态(intrinsic)与外部状态(extrinsic)。以上例来说,字元属于内部状态,而大小、字型等等不同的资讯属于外部状态。<br>
<br>
更详细一些来说明,内部状态是物件可共享的讯息部份,例如在绘制一个英文字串时,重覆的字元部份为内部状态,像是 "ABC is
BAC",其中A、B、C的字元资讯部份不必直接储存于字元物件中,它是属于可以共享的部份,可以将这些可以重复使用的字元储存在Flyweight
Pool中。<br>
<br>
外部状态是物件依赖的一个场景(context),例如绘制字元时的字型资讯、位置资讯等等,绘制一个字元时,先从Flyweight Pool中找出共享的Flyweight,然后从场景中查找对应的绘制资讯(字型、大小、位置等)。<br>
<br>
其实任何学过Java的人就一定使用过Java中运用Flyweight模式的好处,要知道,如果您在程式中使用下面的方式来宣告,则实际上是指向同一个字串物件:<br>
<div style="margin-left: 40px;"><span style="font-weight: bold; font-family: Courier New,Courier,monospace;">String str1 = "flyweight";</span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<span style="font-weight: bold; font-family: Courier New,Courier,monospace;">String str2 = "flyweight"; </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<span style="font-weight: bold; font-family: Courier New,Courier,monospace;">System.out.println(str1 == str2);</span><br>
</div>
<br>
程式的执行结果会显示True,在Java中,会维护一个String Pool,对于一些可以共享的字串物件,会先在String
Pool中查找是否存在相同的String内容(字元相同),如果有就直接传回,而不是直接创造一个新的String物件,以减少记忆体的耗用。<br>
<br>
再来个一看例子,String的intern()方法,我们来看看它的API说明的节录:<br>
<div style="margin-left: 40px;"><span style="font-weight: bold; font-family: Courier New,Courier,monospace;">Returns a canonical representation for the string object.</span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<span style="font-weight: bold; font-family: Courier New,Courier,monospace;">A pool of strings, initially empty, is maintained privately by the class String.</span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<br style="font-weight: bold; font-family: Courier New,Courier,monospace;">
<span style="font-weight: bold; font-family: Courier New,Courier,monospace;">When
the intern method is invoked, if the pool already contains a string
equal to this String object as determined by the equals(Object) method,
then the string from the pool is returned. Otherwise, this String
object is added to the pool and a reference to this String object is
returned.</span><br>
</div>
<br>
这段话其实已说明了Flyweight模式的运作方式,用个实例来说明会更清楚:<br>
<ul>
<li>Main.java</li>
</ul>
<pre>public class Main { <br> public static void main(String[] args) { <br> String str1 = "fly"; <br> String str2 = "weight"; <br> String str3 = "flyweight"; <br> String str4; <br><br> str4 = str1 + str2; <br> System.out.println(str3 == str4); <br><br> str4 = (str1 + str2).intern(); <br> System.out.println(str3 == str4); <br> } <br>}</pre>
<br>
在程式中第一次比较str3与str4物件是否为同一物件时,您知道结果会是false,而intern()方法会先检查 String
Pool中是否存在字元部份相同的字串物件,如果有的话就传回,由于程式中之前已经有"flyweight"字串物件,intern()在String
Pool中发现了它,所以直接传回,这时再进行比较,str3与str4所指向的其实是同一物件,所以结果会是true。<br>
<br>
Flyweight模式在传回物件时,所使用的是工厂模式,使用者并不会知道物件被创造的细节,下图是Flyweight模式的结构图: <br>
<div style="text-align: center;"><img style="width: 450px; height: 265px;" alt="Flyweight" title="Flyweight" src="images/flyweight-1.jpg"><br>
</div>
<br>
之前举的例子是针对物件的内部状态所作的说明,那么字型资讯等外部的设定呢?一两个简单的外部资讯设定可以直接写死(hard code)在程式中,例如简单的使用介面字型设定。<br>
<br>
但如果是文书处理器呢?使用者设定字型、大小等资讯会是动态的呢?Gof书中将字型资讯作为是绘制字元的外部状态,使用一个Context
物件来维护外部状态资料库,每次要绘制字元物件时,这个Context物件会被作为参数传递给字元物件,字元物件透过查找Context中的资料来获得字
型资讯,从而进行正确的场景绘制。<br>
<br>
外部状态维护与内部状态之间的对应关系,在查找时,Gof书中所使用的是BTree?结构,由于查找必须花费时间,所以这也指出了使用Flyweight
模式所必须付出的代价:以时间换取空间。如何设计外部状态的资料结构,以使得查找时间缩短,这是另一个重要的课题(不过就不是这篇文章要讨论的课题了)。<br>
<br>
补充:关于字元(内部状态)及字型、大小(外部状态)之间的对应问题通常不太需要程式设计人员的关心,因为通常可以找的到一些现成的图型介面API,它们都设计好一些相关元件,直接使用就可以了。<br>
<br>
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -