05-06-設計模式 享元模式
阿新 • • 發佈:2022-05-27
展示網站專案需求
小型的外包專案, 給客戶A做一個產品展示網站, 客戶A的朋友感覺效果很不錯, 也需要做這樣的產品網站, 但是要求都有些不同
- 有客戶要求以新聞的方式釋出
- 有客戶要求以部落格的形式釋出
- 有客戶希望以微信公眾號的形式釋出
傳統解決方案
- 直接複製貼上一份, 然後根據客戶不同的要求, 進行定製修改(之前我待過的一家公司,也是這樣做的)
- 給每個網站租用一個空間
傳統方案問題分析
- 需要的網站結構相似度很高,而且都不是高訪問量網站,如果分成多個虛擬空間來處理,相當於一個相同網站的例項物件很多,造成伺服器的資源浪費
- 解決思路:整合到一個網站中,共享其相關的程式碼和資料,對於硬碟、記憶體、CPU、資料庫空間等伺服器資源都可以達成共享,減少伺服器資源
- 對於程式碼來說,由於是一份例項,維護和擴充套件都更加容易
- 上面的解決思路就可以使用享元模式來解決
享元模式
基本介紹
- 享元模式(FlyweightPattern)也叫蠅量模式:運用共享技術有效地支援大量細粒度的物件
- 常用於系統底層開發,解決系統的效能問題。像資料庫連線池,裡面都是建立好的連線物件,在這些連線物件中有我們需要的則直接拿來用,避免重新建立,如果沒有我們需要的,則建立一個
- 享元模式能夠解決重複物件的記憶體浪費的問題,當系統中有大量相似物件,需要緩衝池時。不需總是建立新物件,可以從緩衝池裡拿。這樣可以降低系統記憶體,同時提高效率
- 享元模式經典的應用場景就是池技術了,String常量池、資料庫連線池、緩衝池等等都是享元模式的應用,享元模式是池技術的重要實現方式
享元模式的原理類圖
對類圖的說明對原理圖的說明-即(模式的角色及職責)
- FlyWeight是抽象的享元角色,他是產品的抽象類,同時定義出物件的外部狀態和內部狀態(後面介紹)的介面或實現
- ConcreteFlyWeight是具體的享元角色,是具體的產品類,實現抽象角色定義相關業務
- UnSharedConcreteFlyWeight是不可共享的角色,一般不會出現在享元工廠。
- FlyWeightFactory享元工廠類,用於構建一個池容器(集合),同時提供從池中獲取物件方法
內部狀態和外部狀態
比如圍棋、五子棋、跳棋,它們都有大量的棋子物件,圍棋和五子棋只有黑白兩色,跳棋顏色多一點,所以棋子顏色就是棋子的內部狀態;而各個棋子之間的差別就是位置的不同,當我們落子後,落子顏色是定的,但位置是變化的,所以棋子座標就是棋子的外部狀態
- 享元模式提出了兩個要求:細粒度和共享物件。這裡就涉及到內部狀態和外部狀態了,即將物件的資訊分為兩個部分:內部狀態和外部狀態
- 內部狀態指物件共享出來的資訊,儲存在享元物件內部且不會隨環境的改變而改變
- 外部狀態指物件得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
- 舉個例子:圍棋理論上有361個空位可以放棋子,每盤棋都有可能有兩三百個棋子物件產生,因為記憶體空間有限,一臺伺服器很難支援更多的玩家玩圍棋遊戲,如果用享元模式來處理棋子,那麼棋子物件就可以減少到只有兩個例項,這樣就很好的解決了物件的開銷問題
享元模式解決方案
類圖
程式碼實現
package com.flower.xiangyuan; import lombok.AllArgsConstructor; import lombok.Data; import java.util.HashMap; import java.util.Map; import java.util.Optional; public class TestMain { public static void main(String[] args) { WebSiteFactory webSiteFactory = new WebSiteFactory(); WebSite xw = webSiteFactory.getWebSite("新聞"); WebSite bk = webSiteFactory.getWebSite("部落格"); WebSite wechat = webSiteFactory.getWebSite("微信公眾號"); xw.use(new User("張三")); bk.use(new User("李四")); wechat.use(new User("王五")); Integer webSiteCount = webSiteFactory.getWebSiteCount(); System.out.println("網站總數:"+webSiteCount); } } abstract class WebSite{ public abstract void use(User user); } class ConcreteWebSite extends WebSite{ // 網站釋出的形式 內部狀態 private String type = ""; public ConcreteWebSite(String type) { this.type = type; } @Override public void use(User user) { System.out.println("網站的釋出形式為:"+type + ", " + user.getName() +" 正在使用"); } } class WebSiteFactory{ private Map<String,ConcreteWebSite> map = new HashMap<>(); // 根據型別返回網站 public WebSite getWebSite(String type){ return Optional.ofNullable(map.putIfAbsent(type, new ConcreteWebSite(type))).orElseGet(() -> map.get(type)); } // 統計總數 public Integer getWebSiteCount(){ return map.size(); } } @Data @AllArgsConstructor class User{ private String name; }
原始碼剖析
JDK原始碼
Integer原始碼
享元模式的注意事項和細節
其實我感覺這個模式的舉例並不好, 應為在真正的開發中, 不可能, 我多個客戶, 你給人家部署一套程式碼, 但是這個模式的思想還是很好的
- 在享元模式這樣理解,“享”就表示共享,“元”表示物件
- 系統中有大量物件,這些物件消耗大量記憶體,並且物件的狀態大部分可以外部化時,我們就可以考慮選用享元模式
- 用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識碼所標識的物件,用HashMap/HashTable儲存
- 享元模式大大減少了物件的建立,降低了程式記憶體的佔用,提高效率
- 享元模式提高了系統的複雜度。需要分離出內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨著內部狀態的改變而改變,這是我們使用享元模式需要注意的地方.
- 使用享元模式時,注意劃分內部狀態和外部狀態,並且需要有一個工廠類加以控制。
- 享元模式經典的應用場景是需要緩衝池的場景,比如String常量池、資料庫連線池