Dashboard > Design Patterns > Flyweight
Design Patterns Log In   View a printable version of the current page.
Flyweight
Added by Thomas Yuan, last edited by koji lin on Oct 17, 2004  (view change)
Labels: 
(None)

資料來源: Java 技術論壇
作者: 良葛格(caterpillar)

在Gof的書中指出,享元(flyweight)的目的在於運用共享技術,使得一些細粒度的物件可以共享。在書中
所舉出的例子是文檔編輯器中的字元物件,若每個字元物件會包括字元、大小、字型等等不同的資訊,想想一
篇文章中可能出現多少字元,如果我們為每一個字元都使用一個物件來完整描述有關於它的訊息,那麼一篇文
字中將會耗用多少的記憶體?!

『flyweight』在大陸書籍中有的會翻讀為「享元」,我覺得這個翻譯不錯,「共享的元件」,我們考慮數量多
且性質相近的物件時,將該物件的資訊分為兩個部份---內部狀態(intrinsic)與外部狀態(extrinsic):

內部狀態~
 是物件可共享的訊息部份,例如在繪製一個英文字串時,重覆的字元部份我們就可以將之設定為內部狀態,
 "ABC is BAC",其中 A、B、C 的字元資訊部份不必直接儲存於字元物件中,它是屬於可以共享的部份。

外部狀態~
 是物件依賴的一個場景(context),例如繪製字元時的字型資訊、位置資訊等等,我們繪製一個字元時,
 先從flyweight pool中找出共享的享元,然後從Context中查找對應的繪製資訊(字型、大小、位置等)。

這個例子看起來很複雜對吧!我一開始看也覺得複雜,後來看懂之後,發現享元模式的概念其實並不難,麻
煩的是如何對應共享資訊與非共享資訊之間的關係,在Gof的書中有很大部份在描述這種關係,它使用BTree
來進行關係的對應。。。。總之,容易該初學享元模式的人模糊了焦點。

我們先專注於享元的內部狀態描述。其實任何學過Java的人就一定使用過Java中運用享元模式的好處,我們
知道,如果您在程式中使用下面的方式來宣告,則實際上是指向同一個字串物件:

String str1 = "flyweight";
String str2 = "flyweight";
System.out.println(str1 = str2);

程式的執行結果會顯示True,在Java中,會維護一個String Pool,對於一些可以共享的字串物件,會先在String Pool中查找是否存在相同的String內容(字元相同),如果有就直接傳回,而不是直接創造一個新的String物件,以減少記憶體的耗用。

再來個一看例子,String的intern()方法,我們來看看它的API說明的節錄:


Returns a canonical representation for the string object. A pool of strings, initially empty, is maintained privately by the class String. 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.

上述紅色字這段話其實已說明了享元模式的運作方式,我們用個實例來說明會更清楚:

public class Main \{
 
 public static void main(String[] args) \{
  
  String str1 = "fly";
  String str2 = "weight";
  String str3 = "flyweight";
  String str4;
  
  str4 = str1 + str2;
  System.out.println(str3 ==str4);
  str4 = (str1 + str2).intern();
  System.out.println(str3 == str4);
 \}
\}

在程式中第一次比較str3與str4物件是否為同一物件時,您知道結果會是false,而intern()方法會先檢查String Pool中是否存在字元部份相同的字串物件,如果有的話就傳回,由於程式中之前已經有"flyweight"字串物件,intern()在String Pool中發現了它,所以直接傳回,這時再進行比較,str3與str4所指向的其實是同一物件,所以結果會是true。

享元模式在傳回物件時,所使用的是工廠模式,使用者並不會知道物件被創造的細節,下圖是享元模式的UML結構圖:

之前舉的例子是針對物件的內部狀態所作的說明,再回到Gof的書中例子,Gof書中將字型資訊作為是繪製字元的外部狀態,使用一個Context物件來維護外部狀態資料庫,每次要繪製字元物件時,這個Context物件會被作為參數傳遞給字元物件,字元物件透過查找Context中的資料來獲得字型資訊,從而進行正確的場景繪製。

外部狀態維護與內部狀態之間的對應關係,在查找時,Gof書中所使用的是BTree結構,由於查找必須花費時間,所以這也指出了使用享元模式所必須付出的代價:以時間換取空間。如何設計外部狀態的資料結構,以使得查找時間縮短,這是另一個重要的課題(不過就不是這篇文章要討論的課題了)。

Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.1.5a Build:#411 Mar 16, 2006) - Bug/feature request - Contact Administrators