動(dòng)機(jī)
有些程序需要大量帶有內(nèi)部共享狀態(tài)的對(duì)象實(shí)例。作為例子,我們?cè)O(shè)想一個(gè)戰(zhàn)爭(zhēng)游戲,里邊存在很多 soldier 對(duì)象,soldier 對(duì)象維持著士兵行為的圖形化表示,如移動(dòng)和射擊,另外還有士兵的生命值和在陣地中的位置。雖然創(chuàng)建大量的 soldier 對(duì)象是必要的,但這會(huì)消耗大量的內(nèi)存。
目的
此模式的目的是利用狀態(tài)共享來(lái)支撐需要大量實(shí)例對(duì)象的場(chǎng)景,這些對(duì)象有部分相同的內(nèi)部狀態(tài),另外一些則不一致。
實(shí)現(xiàn)
以下是享元模式的 UML 類圖 (Extrinsic State: 外部狀態(tài), Intrinsic State: 內(nèi)部狀態(tài) )

- Flyweight 聲明一個(gè)接口,通過(guò)這個(gè)接口享元接收并根據(jù)外部狀態(tài)采取行動(dòng)
-
ConcreteFlyweight 實(shí)現(xiàn)
Flyweight接口并保存內(nèi)部狀態(tài)。ConcreteFlyweight對(duì)象必須是可共享的。享元實(shí)例對(duì)象必須在保持內(nèi)部狀態(tài)的同時(shí)能夠?qū)ν獠繝顟B(tài)進(jìn)行操作。在戰(zhàn)爭(zhēng)游戲的例子里,圖形表示是內(nèi)部狀態(tài),位置和生命值是外部狀態(tài)。士兵移動(dòng)過(guò)程中,動(dòng)作行為操作位置這個(gè)外部狀態(tài)從而產(chǎn)生新的位置。 -
FlyweightFactory 此工廠新建和管理享元對(duì)象。此外,享元工廠確保享元對(duì)象的共享。工廠維持著一個(gè)包含各類享元的對(duì)象池。如果一類對(duì)象已經(jīng)創(chuàng)建過(guò)了,則直接從對(duì)象池中返回。如果是新的對(duì)象,則將其添加入對(duì)象池中。
在戰(zhàn)爭(zhēng)游戲的例子中,士兵的享元工廠創(chuàng)建兩種類型的享元:士兵享元和上校享元。當(dāng)客戶端請(qǐng)求獲取一個(gè)士兵時(shí),享元工廠檢查對(duì)象池中是否已有士兵對(duì)象,有的話直接返回,沒(méi)有的話就新建一個(gè)士兵對(duì)象,塞入池中并返回給客戶端;下一次客戶端再請(qǐng)求士兵時(shí),將直接返回先前創(chuàng)建的士兵對(duì)象,不會(huì)再創(chuàng)建新的。 - Client 客戶端維持享元對(duì)象的引用,此外還計(jì)算和維持外部狀態(tài)
如果客戶端需要一個(gè)享元對(duì)象,它會(huì)訪問(wèn)工廠來(lái)獲取。工廠檢查享元對(duì)象池,看請(qǐng)求的對(duì)象類型是否在池中,如果存在,則返回該對(duì)象的引用;如果不存在,工廠會(huì)創(chuàng)建該類對(duì)象,將其加入對(duì)象池中,并將其引用返回給客戶端。享元維持內(nèi)部狀態(tài)(我們創(chuàng)建享元來(lái)在大量對(duì)象中共享的狀態(tài))并提供方法來(lái)操作外部狀態(tài)(這些狀態(tài)在各個(gè)對(duì)象中各不相同)
適用范圍和例子
享元模式適用于適用存在大量對(duì)象的程序,并且這些對(duì)象的一部分內(nèi)部狀態(tài)可以共享,另外一部分狀態(tài)則有所不同。
示例 - 戰(zhàn)爭(zhēng)游戲
游戲?qū)嵗?個(gè) SoldierClient, 每個(gè) client 負(fù)責(zé)維護(hù)自己的內(nèi)部狀態(tài),這些狀態(tài)對(duì)于士兵享元來(lái)說(shuō)是外部的。盡管實(shí)例化了5個(gè) client, 但是僅使用了一個(gè) Soldier 享元對(duì)象。

源碼:design-patterns/flyweigh/Soldier
再一個(gè)例子 - 文本編輯器
面向?qū)ο蟮奈谋揪庉嬈餍枰獎(jiǎng)?chuàng)建字符對(duì)象 Character 來(lái)表示文件中的每個(gè)字符。每個(gè)字符對(duì)象 Character 維護(hù)著它是什么字符,是什么字體,字符的大小以及它在文件中的位置等信息。一個(gè)文件通常由及其龐大的字符對(duì)象集組成,也就會(huì)占用非常多的內(nèi)存。注意,一般來(lái)說(shuō)字符的數(shù)量(數(shù)字,字母和其他特殊字符)是已知并且固定的,而且能應(yīng)用于每個(gè)字符上的字體也是已知的;所以,通過(guò)創(chuàng)建管理字符類型信息(字母,數(shù)字等)和字體信息的 Letter 字母享元,然后再創(chuàng)建只維護(hù)字符在文件中位置的Letter Client 字母消費(fèi)者對(duì)象, 我們就大大降低了編輯器的內(nèi)存需求。
源碼: design-patterns/flyweigh/character
總結(jié)
享元模式通過(guò)在消費(fèi)端共享享元對(duì)象來(lái)節(jié)省內(nèi)存消耗。所能節(jié)省的內(nèi)存量取決于享元種類的個(gè)數(shù)(如上面例子中討論的士兵分類和上校分類)。
相關(guān)模式
工廠 和 單例模式 - 享元模式一般會(huì)使用一個(gè)生產(chǎn)享元的工廠,并將單例應(yīng)用于這個(gè)工廠,這樣每個(gè)種類的享元只會(huì)返回一個(gè)實(shí)例。
狀態(tài) 和 策略模式 - 狀態(tài)和策略模式的對(duì)象也經(jīng)常實(shí)現(xiàn)為享元模式
JDK中的應(yīng)用
java.lang.Integer#valueOf(int) (also on Boolean, Byte, Character, Short, Long and BigDecimal)
Long 的內(nèi)部類 LongCache 即為享元對(duì)象池,其中緩存了 [-128, 127] 范圍的長(zhǎng)整型包裝類實(shí)例; 如果通過(guò) Long.valueOf(n) 獲取實(shí)例,在范圍內(nèi)的就直接從 LongCache 中返回; Long 在其內(nèi)部保存基本類型 long (注意是小寫)的值,并提供 parseLong, intValue, floatValue, longValue 等方法返回基本類型值的外部狀態(tài),正是符合享元模式的使用場(chǎng)景。