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

單純享元模式所涉及到的角色如下:
- 抽象享元(Flyweight)角色:給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色需要實(shí)現(xiàn)的方法。
- 具體享元(ConcreteFlyweight)角色:實(shí)現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
- 享元工廠(FlyweightFactory)角色:本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)墓蚕?。?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)又一個(gè)符合要求的享元對(duì)象。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒(méi)有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)合適的享元對(duì)象。
示例代碼
抽象享元角色類(lèi)
public interface Flyweight {
//一個(gè)示意性方法,參數(shù)state時(shí)外運(yùn)狀態(tài),由外部傳入
public void operation(String state);
}
具體享元角色類(lèi)ConcreteFlyweight有一個(gè)內(nèi)蘊(yùn)狀態(tài),在本例中,使用Character類(lèi)型的intrinsicState屬性代表,它的值應(yīng)當(dāng)是在享元模式被創(chuàng)建時(shí)賦予。所有的內(nèi)蘊(yùn)狀態(tài),在對(duì)象創(chuàng)建完成之后,就不會(huì)再改變了。
如果一個(gè)享元模式有內(nèi)蘊(yùn)狀態(tài)的話,所有的外部狀態(tài)必須存儲(chǔ)在客戶端,在使用享元對(duì)象時(shí),再由客戶端傳入享元對(duì)象。這里只有一個(gè)外蘊(yùn)狀態(tài),operation(String state)的state參數(shù)就是由外部傳入的外蘊(yùn)狀態(tài)。
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
public ConcreteFlyweight(Character intrinsicState) {
this.intrinsicState = intrinsicState;
}
/**
* 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為
* 但是并不改變方法的內(nèi)蘊(yùn)狀態(tài)
*/
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
享元工廠角色類(lèi)。必須指出的是,客戶端不可以直接將具體享元類(lèi)實(shí)例化,而必須通過(guò)一個(gè)工廠對(duì)象,利用一個(gè)factory()方法得到享元對(duì)象。一般而言,享元工廠對(duì)象在整個(gè)系統(tǒng)中只有一個(gè),因此也可以使用單例模式。其實(shí)在這個(gè)例子中,單例模式跟享元模式可以替代使用。
當(dāng)客戶端需要單純享元對(duì)象的時(shí)候,需要調(diào)用享元工廠的factory()方法,并傳入所需的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài),由工廠方法產(chǎn)生所需要的享元對(duì)象。
public class FlyweightFactory {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
public Flyweight factory(Character state) {
//先從已有的緩存列表中查詢對(duì)象是否已存在
Flyweight flyweight = files.get(state);
if (flyweight == null) {
//如果對(duì)象不存在,則重新創(chuàng)建一個(gè)新的Flyweight對(duì)象
flyweight = new ConcreteFlyweight(state);
//將新生成的對(duì)象放入緩存列表中
files.put(state, flyweight);
}
//返回對(duì)象
return flyweight;
}
}
客戶端類(lèi)
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);
}
}
雖然客戶端申請(qǐng)了三個(gè)享元對(duì)象,但是實(shí)際創(chuàng)建的享元對(duì)象只有兩個(gè),這就是共享的含義,運(yùn)行的結(jié)果如下:
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
復(fù)合享元模式
在單純享元模式中,所有的享元對(duì)象都是單純享元對(duì)象,也就是說(shuō)是可以共享的。還有一種較為復(fù)雜的情況,將一些單純享元對(duì)象使用合成模式加以復(fù)合,形成復(fù)合享元對(duì)象。這樣的復(fù)合對(duì)象本身不能共享,但是它們可以分解為單純享元對(duì)象,而后者則可以共享。

復(fù)合享元角色所涉及到的角色如下:
- 抽象享元角色(Flyweight):給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色所需要實(shí)現(xiàn)的方法。
- 具體享元角色(ConcreteFlyweight):實(shí)現(xiàn)抽象享元角色所規(guī)定的借口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
- 復(fù)合享元角色(ConcreteCompositeFlyweight):復(fù)合享元角色所代表的對(duì)象是不可以共享的,但是一個(gè)復(fù)合享元對(duì)象可以分解成為多個(gè)本身是單純享元對(duì)象的組合。復(fù)合享元對(duì)象又稱(chēng)作不可共享的享元對(duì)象。
- 享元工廠角色(FlyweightFactory):本角色需要負(fù)責(zé)創(chuàng)建和管理角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)墓蚕怼.?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)有一個(gè)符合要求的享元對(duì)象。如果已經(jīng)存在,則享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒(méi)有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,則享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)合適的享元對(duì)象。
示例代碼
抽象享元角色類(lèi)
public interface Flyweight {
//一個(gè)示意性方法,參數(shù)state時(shí)外運(yùn)狀態(tài),由外部傳入
public void operation(String state);
}
具體享元角色類(lèi)
public class ConcreteFlyweight implements Flyweight {
private Character intrinsicState = null;
public ConcreteFlyweight(Character intrinsicState) {
this.intrinsicState = intrinsicState;
}
/**
* 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為
* 但是并不改變方法的內(nèi)蘊(yùn)狀態(tài)
*/
@Override
public void operation(String state) {
System.out.println("Intrinsic State = " + this.intrinsicState);
System.out.println("Extrinsic State = " + state);
}
}
復(fù)合享元對(duì)象是由單純享元對(duì)象通過(guò)復(fù)合而成的,因此它提供了add()這樣的聚集管理方法。由于 一個(gè)復(fù)合享元對(duì)象具有不同的聚集元素,這些聚集元素在復(fù)合享元對(duì)象被創(chuàng)建之后加入,這本身就意味著復(fù)合享元對(duì)象的狀態(tài)是會(huì)改變的,因此復(fù)合享元對(duì)象是不能共享的。
復(fù)合享元角色實(shí)現(xiàn)了抽象享元角色所規(guī)定的借口,也就是operation()方法,這個(gè)方法有一個(gè)參數(shù),代表復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)。一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等的;而一個(gè)復(fù)合享元對(duì)象所包含的所有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不相等的,不然就沒(méi)有了使用的價(jià)值。
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
/**
* 添加一個(gè)新的單純享元對(duì)象到聚集中
* @param key
* @param flyweight
*/
public void add(Character key, Flyweight flyweight) {
files.put(key, flyweight);
}
/**
* 外蘊(yùn)狀態(tài)作為參數(shù)傳遞到方法中
*/
@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);
}
}
}
享元工廠角色提供兩種不同的方法,一種用于提供單純享元對(duì)象,一種用于提供復(fù)合享元對(duì)象。
public class FlyweightFactory {
private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
/**
* 復(fù)合享元工廠方法
* @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) {
//先從已有的緩存列表中查詢對(duì)象是否已存在
Flyweight flyweight = files.get(state);
if (flyweight == null) {
//如果對(duì)象不存在,則重新創(chuàng)建一個(gè)新的Flyweight對(duì)象
flyweight = new ConcreteFlyweight(state);
//將新生成的對(duì)象放入緩存列表中
files.put(state, flyweight);
}
//返回對(duì)象
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("復(fù)合享元模式是否可以共享對(duì)象:" + (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("單純享元模式是否可以共享對(duì)象:" + (flyweight1 == flyweight2));
}
}
運(yùn)行結(jié)果如下:
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
---------------------------------------------
復(fù)合享元模式是否可以共享對(duì)象:false
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@7852e922
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@4e25154f
單純享元模式是否可以共享對(duì)象:true
從運(yùn)行結(jié)果可以看出:
- 一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等,也就是上面例子中的
Composite1 Call。 - 一個(gè)復(fù)合享元對(duì)象所含有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不想等的,也就是
A、B、C。 - 復(fù)合享元對(duì)象是不能共享的。也就是說(shuō),使用相同的對(duì)象
compositeState通過(guò)享元工廠角色分別兩次創(chuàng)建出的對(duì)象不是同一個(gè)對(duì)象。 - 單純享元對(duì)象是可以共享的。也就是相同的對(duì)象
state通過(guò)享元工廠角色分別多次創(chuàng)建出的對(duì)象是同一個(gè)對(duì)象。
享元模式的優(yōu)缺點(diǎn)
享元模式的優(yōu)點(diǎn)在于它大幅度的降低內(nèi)存中對(duì)象的數(shù)量。但是,它做到這一點(diǎn)所付出的代價(jià)也是很高的:
- 享元模式使得系統(tǒng)更加復(fù)雜。為了使對(duì)象可以共享,需要將一些狀態(tài)外部化,這樣使得程序的邏輯復(fù)雜化。
- 享元模式將享元對(duì)象的狀態(tài)外部化,而讀取外部狀態(tài)使得運(yùn)行時(shí)間稍微變長(zhǎng)。