定義
Flyweight在拳擊比賽中指最輕量級,即“蠅量級”或“雨量級”。這里選擇使用“享元模式”的意譯,是因為這樣更能反映出模式的用意。享元模式是對象的結構模式。享元模式以共享的方式高效的支持大量的細粒度對象。
Java中的String類型
在Java語言中,String類型就是使用了享元模式。String對象是final類型,對象一旦創建就不可改變。在Java中字符串常量都是存儲在常量池中的,Java會確保一個字符串常量在常量池中只有一個拷貝。String a = "abc"
,其中"abc"
就是一個字符串常量。
public class Test {
public static void main(String[] args) {
String a = "abc";
String b = "abc";
System.out.println(a==b);
}
}
上面的例子中,結果為true
,這說明a和b兩個引用都指向了常量池中的同一個字符串常量abc
。這樣的設計避免了在創建N多個相同對象時所產生的不必要的大量的資源消耗。
享元模式的結構
享元模式采用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是內存的損耗。享元對象能做到共享的關鍵在于能區分內蘊狀態(Internal State)和外蘊狀態(External State)。
一個內蘊狀態是存儲在享元對象內部的,并且是不會隨環境的改變而有所不同。因此,一個享元可以有內蘊狀態并可以共享。
一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元對象的外蘊狀態必須由客戶端保存,并在享元對象被創建之后,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。
享元模式可以分為單純享元模式和復合享元模式兩種形勢。
單純享元模式
在單純享元模式中,所有的享元對象都是可以共享的。

單純享元模式所涉及到的角色如下:
- 抽象享元(Flyweight)角色:給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。
- 具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責為內蘊狀態提供存儲空間。
- 享元工廠(FlyweightFactory)角色:本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當的共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經又一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。
示例代碼
抽象享元角色類
public interface Flyweight {
//一個示意性方法,參數state時外運狀態,由外部傳入
public void operation(String state);
}
具體享元角色類ConcreteFlyweight
有一個內蘊狀態,在本例中,使用Character
類型的intrinsicState
屬性代表,它的值應當是在享元模式被創建時賦予。所有的內蘊狀態,在對象創建完成之后,就不會再改變了。
如果一個享元模式有內蘊狀態的話,所有的外部狀態必須存儲在客戶端,在使用享元對象時,再由客戶端傳入享元對象。這里只有一個外蘊狀態,operation(String state)
的state
參數就是由外部傳入的外蘊狀態。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
public ConcreteFlyweight(Character intrinsicState) {
this.intrinsicState = intrinsicState;
}
/**
* 外蘊狀態作為參數傳入方法中,改變方法的行為
* 但是并不改變方法的內蘊狀態
*/
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
享元工廠角色類。必須指出的是,客戶端不可以直接將具體享元類實例化,而必須通過一個工廠對象,利用一個factory()
方法得到享元對象。一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式。其實在這個例子中,單例模式跟享元模式可以替代使用。
當客戶端需要單純享元對象的時候,需要調用享元工廠的factory()
方法,并傳入所需的單純享元對象的內蘊狀態,由工廠方法產生所需要的享元對象。
public class FlyweightFactory {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
public Flyweight factory(Character state) {
//先從已有的緩存列表中查詢對象是否已存在
Flyweight flyweight = files.get(state);
if (flyweight == null) {
//如果對象不存在,則重新創建一個新的Flyweight對象
flyweight = new ConcreteFlyweight(state);
//將新生成的對象放入緩存列表中
files.put(state, flyweight);
}
//返回對象
return flyweight;
}
}
客戶端類
public class Client {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweightA = factory.factory(new Character('A'));
flyweightA.operation("First Call , A State");
System.out.println(flyweightA);
Flyweight flyweightB = factory.factory(new Character('B'));
flyweightB.operation("Second Call , B State");
System.out.println(flyweightB);
Flyweight flyweightC = factory.factory(new Character('A'));
flyweightC.operation("Third Call , A State");
System.out.println(flyweightC);
}
}
雖然客戶端申請了三個享元對象,但是實際創建的享元對象只有兩個,這就是共享的含義,運行的結果如下:
Intrinsic State = A
Extrinsic State = First Call , A State
com.sschen.flyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Second Call , B State
com.sschen.flyweight.ConcreteFlyweight@15db9742
Intrinsic State = A
Extrinsic State = Third Call , A State
com.sschen.flyweight.ConcreteFlyweight@2a139a55
復合享元模式
在單純享元模式中,所有的享元對象都是單純享元對象,也就是說是可以共享的。還有一種較為復雜的情況,將一些單純享元對象使用合成模式加以復合,形成復合享元對象。這樣的復合對象本身不能共享,但是它們可以分解為單純享元對象,而后者則可以共享。
復合享元角色所涉及到的角色如下:
- 抽象享元角色(Flyweight):給出一個抽象接口,以規定出所有具體享元角色所需要實現的方法。
- 具體享元角色(ConcreteFlyweight):實現抽象享元角色所規定的借口。如果有內蘊狀態的話,必須負責為內蘊狀態提供存儲空間。
- 復合享元角色(ConcreteCompositeFlyweight):復合享元角色所代表的對象是不可以共享的,但是一個復合享元對象可以分解成為多個本身是單純享元對象的組合。復合享元對象又稱作不可共享的享元對象。
- 享元工廠角色(FlyweightFactory):本角色需要負責創建和管理角色。本角色必須保證享元對象可以被系統適當的共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經存在,則享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,則享元工廠角色就應當創建一個合適的享元對象。
示例代碼
抽象享元角色類
public interface Flyweight {
//一個示意性方法,參數state時外運狀態,由外部傳入
public void operation(String state);
}
具體享元角色類
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
public ConcreteFlyweight(Character intrinsicState) {
this.intrinsicState = intrinsicState;
}
/**
* 外蘊狀態作為參數傳入方法中,改變方法的行為
* 但是并不改變方法的內蘊狀態
*/
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
復合享元對象是由單純享元對象通過復合而成的,因此它提供了add()
這樣的聚集管理方法。由于 一個復合享元對象具有不同的聚集元素,這些聚集元素在復合享元對象被創建之后加入,這本身就意味著復合享元對象的狀態是會改變的,因此復合享元對象是不能共享的。
復合享元角色實現了抽象享元角色所規定的借口,也就是operation()
方法,這個方法有一個參數,代表復合享元對象的外蘊狀態。一個復合享元對象的所有單純享元對象元素的外蘊狀態都是與復合享元對象的外蘊狀態相等的;而一個復合享元對象所包含的所有的單純享元對象的內蘊狀態一般是不相等的,不然就沒有了使用的價值。
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
/**
* 添加一個新的單純享元對象到聚集中
* @param key
* @param flyweight
*/
public void add(Character key, Flyweight flyweight) {
files.put(key, flyweight);
}
/**
* 外蘊狀態作為參數傳遞到方法中
*/
@Override
public void operation(String state) {
Flyweight flyweight = null;
for (Object o: files.keySet()) {
flyweight = files.get(o);
flyweight.operation(state);
System.out.println(flyweight);
}
}
}
享元工廠角色提供兩種不同的方法,一種用于提供單純享元對象,一種用于提供復合享元對象。
public class FlyweightFactory {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
/**
* 復合享元工廠方法
* @param compositeState
* @return
*/
public Flyweight factory(List<Character> compositeState) {
ConcreteCompositeFlyweight concreteCompositeFlyweight = new ConcreteCompositeFlyweight();
for (Character state : compositeState) {
concreteCompositeFlyweight.add(state, this.factory(state));
}
return concreteCompositeFlyweight;
}
/**
* 單純享元工廠方法
* @param state
* @return
*/
public Flyweight factory(Character state) {
//先從已有的緩存列表中查詢對象是否已存在
Flyweight flyweight = files.get(state);
if (flyweight == null) {
//如果對象不存在,則重新創建一個新的Flyweight對象
flyweight = new ConcreteFlyweight(state);
//將新生成的對象放入緩存列表中
files.put(state, flyweight);
}
//返回對象
return flyweight;
}
}
客戶端角色
public class Client {
public static void main(String[] args) {
List<Character> compositeState = new ArrayList<Character>();
compositeState.add('A');
compositeState.add('B');
compositeState.add('C');
compositeState.add('B');
compositeState.add('A');
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight compositeFly1 = flyweightFactory.factory(compositeState);
Flyweight compositeFly2 = flyweightFactory.factory(compositeState);
compositeFly1.operation("Composite1 Call");
compositeFly2.operation("Composite2 Call");
System.out.println("---------------------------------------------");
System.out.println("復合享元模式是否可以共享對象:" + (compositeFly1 == compositeFly2));
System.out.println(compositeFly1);
System.out.println(compositeFly2);
Character charState = 'A';
Flyweight flyweight1 = flyweightFactory.factory(charState);
Flyweight flyweight2 = flyweightFactory.factory(charState);
System.out.println("單純享元模式是否可以共享對象:" + (flyweight1 == flyweight2));
}
}
運行結果如下:
Intrinsic State = A
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@15db9742
Intrinsic State = C
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@6d06d69c
Intrinsic State = A
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@15db9742
Intrinsic State = C
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@6d06d69c
---------------------------------------------
復合享元模式是否可以共享對象:false
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@7852e922
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@4e25154f
單純享元模式是否可以共享對象:true
從運行結果可以看出:
- 一個復合享元對象的所有單純享元對象元素的外蘊狀態都是與復合享元對象的外蘊狀態相等,也就是上面例子中的
Composite1 Call
。 - 一個復合享元對象所含有的單純享元對象的內蘊狀態一般是不想等的,也就是
A
、B
、C
。 - 復合享元對象是不能共享的。也就是說,使用相同的對象
compositeState
通過享元工廠角色分別兩次創建出的對象不是同一個對象。 - 單純享元對象是可以共享的。也就是相同的對象
state
通過享元工廠角色分別多次創建出的對象是同一個對象。
享元模式的優缺點
享元模式的優點在于它大幅度的降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:
- 享元模式使得系統更加復雜。為了使對象可以共享,需要將一些狀態外部化,這樣使得程序的邏輯復雜化。
- 享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。