Flyweight 模式


您現在打算建立一個文書處理程式,文件中的文字可以設置字型資訊,也許您是這麼設定一段文字的字型資訊:
text.setFont(new Font("細明體", Style.BOLD, 12));

文書處理程式中的文字都會有字型資訊,但可能有一些字型資訊是重複的,例如有些文字都是細明體、粗體、大小12的字型,有些文字都是標楷體、 斜體、大小16的字型,對於同樣字型資訊的文字,卻每次都用新建字型物件的方式來設定,雖然是小物件,但數量多的話,也有可能佔用相當的記憶體。

對於小且可以資訊重複的物件,可以考慮共用,例如:
import java.util.*;
import java.lang.ref.*;

interface Style {
final static int PLAIN = 1;
final static int BOLD = 2;
final static int ITALIC = 3;
}

class Font {
final String name;
final int style;
final int size;

Font(String name, int style, int size) {
this.name = name;
this.style = style;
this.size = size;
}

public boolean equals(Object other) {
if (other instanceof Font) {
if (other == this) {
return true;
}
Font font = (Font) other;
return font.style == style
&& font.size == size
&& font.name.equals(this.name);
}
return false;

}
public int hashCode() {
return (style * 37 + size * 13) * name.hashCode();
}
}

class FontFactory {
private static Map<Font, WeakReference<Font>> flyweights =
new WeakHashMap<Font, WeakReference<Font>>();

static Font create(String name, int style, int size) {
Font font = new Font(name, style, size);
if(!flyweights.containsKey(font)) {
flyweights.put(font, new WeakReference<Font>(font));
}
return flyweights.get(font).get();
}
}

public class Main {
public static void main(String[] args) {
Font font1 = FontFactory.create("細明體", Style.BOLD, 12);
Font font2 = FontFactory.create("細明體", Style.BOLD, 12);
System.out.println(font1 == font2);
}
}

在這邊搭配了 Simple Factory 模式, 隱藏了Font建立的細節,最主要是如何共用物件以節省記憶體的使用量,您可以只簡單地使用HashMap,不讓重複資訊 的物件佔用記憶體,而範例程式中則進一步使用了WeakHashMap,這是Java所提供的Map實現,可以在記憶體量 不足時,主動釋放未被程式其它部份參考的物件。

如果是Python實現的話,以下是個簡單示範:
import weakref

class Style:
PLAIN = 1
BOLD = 2
ITALIC = 3

class Font:
def __init__(self, name, style, size):
self.name = name
self.style = style
self.size = size
def __repr__(self):
return repr(self.name) + repr(self.style) + repr(self.size)

class FontFactory:
__flyweight = weakref.WeakValueDictionary()

def create(name, style, size):
font = Font(name, style, size)
if not FontFactory.__flyweight.get(repr(font), None):
FontFactory.__flyweight[repr(font)] = font
return FontFactory.__flyweight[repr(font)]

font1 = FontFactory.create("細明體", Style.BOLD, 12)
font2 = FontFactory.create("細明體", Style.BOLD, 12)
print(font1 is font2)

Java中Flyweight模式的實際應用是字串,如果您在Java程式中使用下面的方式來宣告,則實際上 是指向同一個字串物件:
String str1 = "flyweight";
String str2 = "flyweight";
System.out.println(str1 == str2);
 
程式的執行結果會顯示True,在Java中,會維護一個String Pool,對於一些可以共享的字串物件,會先在String Pool中查找是否存在相同的String內容(字元相同),如果有就直接傳回,而不是直接創造一個新的String物件,以減少記憶體的耗用。