動(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)景牵舵。