Java設計模式學習記錄-享元模式
前言
享元模式也是一種結構型模式,這篇是介紹結構型模式的最后一篇了(因為代理模式很早之前就已經寫過了)。享元模式采用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是內存損耗。
享元模式
定義
享元模式是指運用共享技術有效的支持大量細粒度對象的復用。系統只使用少量的對象,而這些對象都很相似,狀態變化很小,可以實現對象的多次復用。由于享元模式要求能夠共享的對象必須是細粒度對象,因此它又稱為輕量級模式,它是一種對象結構型模式。
舉例
咖啡問題,在一家咖啡店里有幾種口味的咖啡,例如:拿鐵、摩卡、卡布奇諾等等。最近這家店在搞促銷活動,一中午能賣出幾百杯咖啡,那么咖啡的口味就是一種共享的元素。下面用代碼來實現一下這個例子。
定義訂單接口
/** * 訂單接口 */ public interface Order { //賣出咖啡 void sell(); }
具體的訂單實現
public class CoffeeOrder implements Order { //咖啡口味 public String flavor; public CoffeeOrder(String flavor){ this.flavor = flavor; } @Override public void sell() { System.out.println("賣出了一份"+flavor+"的咖啡。"); } }
訂單工廠類
import com.google.common.collect.Maps; import java.util.Map; import java.util.Objects; /** * 訂單工廠類 */ public class CoffeeOrderFactory { private static Map<String,Order> cof = Maps.newHashMap(); /** * 獲得訂單 * @param flavor 口味 * @return */ public static Order getOrder(String flavor){ Order order = cof.get(flavor); if(Objects.isNull(order)){ order = new CoffeeOrder(flavor); cof.put(flavor,order); } return order; } /** * 獲取最終創建的對象個數 * @return */ public static int getSize(){ return cof.size(); } }
測試類
import org.assertj.core.util.Lists; import java.util.List; /** * 測試買咖啡 */ public class MyTest { public static void main(String[] args) { buyCoffee("拿鐵"); buyCoffee("卡布奇諾"); buyCoffee("摩卡"); buyCoffee("拿鐵"); buyCoffee("拿鐵"); buyCoffee("拿鐵"); buyCoffee("卡布奇諾"); buyCoffee("卡布奇諾"); buyCoffee("卡布奇諾"); buyCoffee("摩卡"); buyCoffee("摩卡"); buyCoffee("摩卡"); //打印出賣出的咖啡 coffeeOrderList.stream().forEach(Order::sell); System.out.println("一共賣出去"+coffeeOrderList.size()+"杯咖啡!"); System.out.println("一共生成了"+CoffeeOrderFactory.getSize()+"個Java對象!"); } //訂單列表 public static List<Order> coffeeOrderList = Lists.newArrayList(); /** * 買咖啡 * @param flavor 口味 */ public static void buyCoffee(String flavor){ coffeeOrderList.add(CoffeeOrderFactory.getOrder(flavor)); } }
運行結果
賣出了一份拿鐵的咖啡。
賣出了一份卡布奇諾的咖啡。
賣出了一份摩卡的咖啡。
賣出了一份拿鐵的咖啡。
賣出了一份拿鐵的咖啡。
賣出了一份拿鐵的咖啡。
賣出了一份卡布奇諾的咖啡。
賣出了一份卡布奇諾的咖啡。
賣出了一份卡布奇諾的咖啡。
賣出了一份摩卡的咖啡。
賣出了一份摩卡的咖啡。
賣出了一份摩卡的咖啡。
一共賣出去12杯咖啡!
一共生成了3個Java對象!
從上面的運行結果可以看出來,雖然賣出去了12杯咖啡,但是最終的口味對象只有3個,因為咖啡口味只有在第一次使用的時候創建,后面就直接使用不會再創建了。
享元模式的分析
下面還是來分析一下享元模式的結構吧,結構圖如下:

享元模式涉及到的角色有抽象享元角色、具體享元角色、復合享元角色、享元工廠角色,以及客戶端角色。具體說明如下:
- 抽象享元角色(FlyWeight):此角色是所有具體享元類的父級,為這些類規定出需要實現的公共接口或抽象類。上面例子中的Order接口就是代表的這個角色。
- 具體享元角色(ConcreteFlyweight):實現抽象享元角色所規定的接口。有時候具體享元角色有稱為單純具體享元角色,因為復合享元角色是由單純具體享元角色通過復合而成的。上面例子中的CoffeeOrder就是代表的這個角色。
- 復合享元角色(UnsharableFlyweight):復合享元角色所代表的對象是不可以共享的,但是一個復合享元對象可以分解成為多個本身是單純享元對象的組合。復合對象又稱為不可共享對象。這個角色一般很少用。
- 享元工廠角色(FlyweightFactory):負責創建和管理享元角色。此角色必須保證享元對象可以被系統適當地共享。當客戶端對象請求一個享元對象時,享元工廠角色需要檢查系統中是否已經有一個符合要求的享元對象,如果已經有了享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個新的合適的享元對象。上面例子中的CoffeeOrderFactory就是代表的這個角色。
- 客戶端角色(client):此角色調用享元工廠角色來使用具體享元角色。
享元模式總結
單純享元模式和復合享元模式
標準的享元模式中既包含享元對象又包含非享元對象,但是在實際使用過程中我們會用到具體兩種特殊形式的享元模式:單純享元模式和復合享元模式。
單純享元模式是指,所有的具體享元對象都是可以共享的,不包括非享元對象。
復合享元模式是指,將一些單純享元對象使用組合模式加以組合,還可以形成組合享元對象,這樣的復合享元對象不能共享,但是它可以分解成單純享元對象,分解后就可以共享了。
享元模式的優點
1、可以極大的減少內存中對象的數量,使得相同或相似的對象在內存中只保存一份,從而可以節約系統資源,提高系統性能。
2、享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元對象可以在不同的環境中被共享。
享元模式的缺點
1、享元模式使得系統變得復雜,需要分離出內部狀態和外部狀態,這使得程序的邏輯復雜化。
想了解更多的設計模式請查看Java設計模式學習記錄-GoF設計模式概述。
面試面到懷疑人生,繼續加油吧!
作者:紀莫
歡迎任何形式的轉載,但請務必注明出處。
限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。
歡迎掃描二維碼關注公眾號:Jimoer
文章會同步到公眾號上面,大家一起成長,共同提升技術能力。
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下。
您的鼓勵是博主的最大動力!


浙公網安備 33010602011771號
2、為了使對象可以共享,享元模式需要將享元對象的部分狀態外部化,而讀取外部狀態將使得運行時間變長。
適用場景
一個系統有大量相同或者相似的對象,造成內存的大量耗費。
對象的大部分狀態都可以外部化,可以將這些外部狀態傳入對象中。
在使用享元模式時需要維護一個存儲享元對象的享元池,而這需要耗費一定的系統資源,因此,應當在需要多次重復使用享元對象時才值得使用享元模式。
延伸
在JDK中就有使用享元模式的例子,最常見的就是我們使用的String類,大家都知道String類是被final修飾的,所以不會被繼承,每次變更都會生成一個新的字符串,這樣就有點占內存了。所以如果直接寫出了一個字符串,當后面又寫出了一個同樣的字符串時會自動去堆中(JDK7以上)查看是否已經存在這個字符串了,如果已經存在則直接使用,如果不存在這個時候才在堆中再給開辟一塊空間存儲字符串。
例如下面的例子:
運行結果: