基本需求:
- 小型的外包專案,給客戶A做一個產品展示網站,客戶A的朋友感覺效果不錯,也希望做這樣的產品展示網站,但是要求都有些不同
- 每個客戶要求釋出的方式不一樣,A要求以新聞的方式釋出,B要求以部落格的方式釋出,C要求以公眾號的方式釋出,並且多個釋出人可能選擇用同一種方式釋出
傳統方案:
- 直接複製A客戶的網站,在其上按照B的要求修改,定製化,給每個網站都租用一個空間
- 需要的網站結構相似度很高,而且都不是高訪問量網站,如果分成多個虛擬空間來處理,相當於一個相同網站的例項物件很多,造成伺服器的資源浪費
- 解決思路:整合到一個網站中,共享其相關的程式碼和資料,對於硬碟、記憶體、CPU、資料庫空間等伺服器資源都可以達成共享,減少伺服器資源
基本介紹:
-
享元模式(Flyweight)也叫'蠅量模式',主要用於減少建立物件的數量,以減少記憶體佔用和提高效能。這種型別的設計模式屬於結構型模式,它提供了減少物件數量從而改善應用所需的物件結構的方式。享元模式嘗試重用現有的同類物件,如果未找到匹配的物件,則建立新物件,有匹配的物件,則直接使用
-
運用共享技術有效地支援大量細粒度的物件,主要解決:在有大量物件時,有可能會造成記憶體溢位,我們把其中共同的部分抽象出來,如果有相同的業務請求,直接返回在記憶體中已有的物件,避免重新建立
-
常用於系統底層開發,解決系統的效能問題。像資料庫連線池,裡面都是建立好的連線物件,在這些連線物件中有我們需要的則直接拿來用,避免重新建立,如果沒有我們需要的,則建立一個
-
享元模式能夠解決重複物件的記憶體浪費的問題,當系統中有大量相似物件,需要緩衝池時。不需總是建立新物件,可以從緩衝池裡拿。這樣可以降低系統記憶體,同時提高效率
-
享元模式經典的應用場景就是池技術了,String 常量池、資料庫連線池、緩衝池等等都是享元模式的應用,享元模式是池技術的重要實現方式
-
UML類圖(原理)
- 說明
- FlyWeight 是抽象的享元角色, 他是產品的抽象類, 同時定義出物件的外部狀態和內部狀態的介面或實現
- 享元模式提出了兩個要求:細粒度和共享物件。這裡就涉及到內部狀態和外部狀態了,即將物件的資訊分為兩個部分:內部狀態和外部狀態
- 內部狀態指物件共享出來的資訊, 儲存在享元物件內部且不會隨環境的改變而改變
- 外部狀態指物件得以依賴的一個標記,是 隨環境改變而改變的、不可共享的狀態
- 例如:下五子棋,圍棋等,使用的棋子就可以用享元模式定義,棋子的顏色就是內部狀態(兩種顏色不會改變),棋子在棋盤的位置隨選手的落子而決定就是外部狀態;
- 可在棋子類 建立兩個屬性 一個顏色,一個位置,落子時每次在工廠獲取的都是同一個物件,只不過位置屬性的值不同而已,達到了這兩個棋子物件的複用
- ConcreteFlyWeight 是具體的享元角色,是具體的產品類,實現抽象角色定義相關業務
- UnSharedConcreteFlyWeight 是不可共享的角色,一般不會出現在享元工廠(一般也不會出現)
- FlyWeightFactory 享元工廠類,內部構建一個池容器(集合),同時提供從池中獲取物件方法
- FlyWeight 是抽象的享元角色, 他是產品的抽象類, 同時定義出物件的外部狀態和內部狀態的介面或實現
- 說明
-
UML類圖(案例)
-
說明
- WebSite有不同的釋出狀態,用type表示,也就是其內部狀態,ConcreteWebSite為其實現類,具體的享元角色
- User為外部狀態,不同的User可以用相同的方式去釋出WebSite
- WebSiteFactory為工廠類,用於維護WebSite池
-
程式碼實現
-
public abstract class WebSite { // 享元抽象類 // 此屬性為內部狀態,釋出的形式只有幾種,新聞,部落格,公眾號 protected String type; public WebSite(String type) { this.type = type; } // 使用User類作為外部狀態,不同的User可以釋出同一形式的網站(實際使用的是同一WebSite物件)達到了物件的複用 public abstract void use(User user); } // 具體的享元類 class ConcreteWebSite extends WebSite{ // 具體的享元產品 public ConcreteWebSite(String type) { super(type); } @Override public void use(User user) { System.out.println(user.getName() + "用" + this.type + "形式釋出了網站"); } }
-
@Data @AllArgsConstructor public class User { // WebSite的外部狀態 private String name; }
-
public class WebSiteFactory { // 享元工廠類,內部維護享元產品池 private Map<String, WebSite> webSiteMap = new HashMap<>(); public WebSite getWebSiteCategory(String type) { if (!webSiteMap.containsKey(type)) { // 池中沒有,建立新的享元產品維護到池中,返回新建立的享元產品 webSiteMap.put(type, new ConcreteWebSite(type)); } return webSiteMap.get(type); } // 獲取池中享元產品的數量 public int getWebSiteCount() { return webSiteMap.size(); } }
-
public class Client { public static void main(String[] args) { WebSiteFactory webSiteFactory = new WebSiteFactory(); WebSite webSite1 = webSiteFactory.getWebSiteCategory("新聞"); webSite1.use(new User("張三")); WebSite webSite2 = webSiteFactory.getWebSiteCategory("部落格"); webSite2.use(new User("李四")); WebSite webSite3 = webSiteFactory.getWebSiteCategory("公眾號"); webSite3.use(new User("王五")); WebSite webSite4 = webSiteFactory.getWebSiteCategory("新聞"); webSite4.use(new User("王五")); System.out.println("池中享元物件的數量為:" + webSiteFactory.getWebSiteCount()); // 3 個 System.out.println(webSite1 == webSite4); // true 其實通過享元工廠獲取的是同一個物件 } }
-
jdk原始碼:
-
在jdk原始碼的Integer類中就使用到了享元模式,Integer.valueOf()方法,在-128-127之間返回的都是同一個Integer的快取物件
-
public class Client { public static void main(String[] args) { // Integer類中就使用到了享元模式,Integer.valueOf()方法,在-128-127之間返回的都是同一個Integer的快取物件 // 如果使用 valueOf 方法得到一個 Integer 例項,範圍在 -128 - 127 ,執行速度比 new 快 Integer w = Integer.valueOf(127); Integer x = new Integer(127); Integer y = Integer.valueOf(127); Integer z = new Integer(127); System.out.println(w == x); // false System.out.println(w == y); // true System.out.println(x == z); // false new出來肯定不一樣 在堆記憶體進行分配 } }
-
// 原始碼部分 public class Integer { ...... public static Integer valueOf(int i) { // 呼叫該方法在-128-127內就返回快取物件,使用的享元模式 if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } ...... // 靜態內部類 IntegerCache private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } // 為Integer.high賦值 high = h; cache = new Integer[(high - low) + 1]; int j = low; // 迴圈建立Integer物件,並放入緩衝池,此處緩衝池使用的是陣列 for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } }
注意事項:
- 在享元模式這樣理解,“享”就表示共享,“元”表示物件系統中有大量物件,這些物件消耗大量記憶體,並且物件的狀態大部分可以外部化時,我們就可以考慮選用享元模式
- 用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識碼所標識的物件,用 HashMap/HashTable 儲存,大大減少了物件的建立,降低了程式記憶體的佔用,提高效率
- 享元模式提高了 系統的複雜度。需要分離出 內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨著內部狀態的改變而改變,這是我們使用享元模式需要注意的地方
- 使用享元模式時,注意劃分內部狀態和外部狀態,並且需要有一個工廠類加以控制
- 享元模式經典的應用場景是需要緩衝池的場景,比如 String 常量池、資料庫連線池