享元模式也是一種結構型模式,這篇是介紹結構型模式的最後一篇了(由於代理模式很早以前就已經寫過了)。享元模式採用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最多見、最直觀的就是內存損耗。html
享元模式是指運用共享技術有效的支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式,它是一種對象結構型模式。java
咖啡問題,在一家咖啡店裏有幾種口味的咖啡,例如:拿鐵、摩卡、卡布奇諾等等。最近這家店在搞促銷活動,一中午能賣出幾百杯咖啡,那麼咖啡的口味就是一種共享的元素。下面用代碼來實現一下這個例子。面試
定義訂單接口設計模式
/** * 訂單接口 */ public interface Order { //賣出咖啡 void sell(); }
具體的訂單實現ide
public class CoffeeOrder implements Order { //咖啡口味 public String flavor; public CoffeeOrder(String flavor){ this.flavor = flavor; } @Override public void sell() { System.out.println("賣出了一份"+flavor+"的咖啡。"); } }
訂單工廠類post
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個,由於咖啡口味只有在第一次使用的時候建立,後面就直接使用不會再建立了。測試
下面仍是來分析一下享元模式的結構吧,結構圖以下:this
享元模式涉及到的角色有抽象享元角色、具體享元角色、複合享元角色、享元工廠角色,以及客戶端角色。具體說明以下:
標準的享元模式中既包含享元對象又包含非享元對象,可是在實際使用過程當中咱們會用到具體兩種特殊形式的享元模式:單純享元模式和複合享元模式。
單純享元模式是指,全部的具體享元對象都是能夠共享的,不包括非享元對象。
複合享元模式是指,將一些單純享元對象使用組合模式加以組合,還能夠造成組合享元對象,這樣的複合享元對象不能共享,可是它能夠分解成單純享元對象,分解後就能夠共享了。
一、能夠極大的減小內存中對象的數量,使得相同或類似的對象在內存中只保存一份,從而能夠節約系統資源,提升系統性能。
二、享元模式的外部狀態相對獨立,並且不會影響其內部狀態,從而使得享元對象能夠在不一樣的環境中被共享。
一、享元模式使得系統變得複雜,須要分離出內部狀態和外部狀態,這使得程序的邏輯複雜化。
想了解更多的設計模式請查看Java設計模式學習記錄-GoF設計模式概述。
面試面到懷疑人生,繼續加油吧!
二、爲了使對象能夠共享,享元模式須要將享元對象的部分狀態外部化,而讀取外部狀態將使得運行時間變長。
適用場景
一個系統有大量相同或者類似的對象,形成內存的大量耗費。
對象的大部分狀態均可之外部化,能夠將這些外部狀態傳入對象中。
在使用享元模式時須要維護一個存儲享元對象的享元池,而這須要耗費必定的系統資源,所以,應當在須要屢次重複使用享元對象時才值得使用享元模式。
延伸
在JDK中就有使用享元模式的例子,最多見的就是咱們使用的String類,你們都知道String類是被final修飾的,因此不會被繼承,每次變動都會生成一個新的字符串,這樣就有點佔內存了。因此若是直接寫出了一個字符串,當後面又寫出了一個一樣的字符串時會自動去堆中(JDK7以上)查看是否已經存在這個字符串了,若是已經存在則直接使用,若是不存在這個時候纔在堆中再給開闢一塊空間存儲字符串。
例以下面的例子:
運行結果: