今天一塊兒來看一個新的設計模式,那就是享元模式,關於此模式,常見的就是 「項目外包」、
以及 「五子棋」 這樣兩個例子,咱們下面就選擇使用 「項目外包」 這個例子引入去講java
程序員小B,幫助客戶 A 作了一個展現一些產品內容的網站,經過 A 的 推薦,客戶 B 、客戶C 也想要作這樣一個網站,可是就是形式有一些變化程序員
並且他們都但願可以下降一些費用,可是每個空間部署着一個網站,因此租借空間的費用是固定的,同時程序員小B 並不想從本身的勞動報酬中縮減費用web
先說最簡單能想到的方案,直接把網站代碼複製幾份,而後每個都租借一個空間,而後對代碼進行定製修改。注:這裏還沒考慮優化或者省錢設計模式
咱們用一個 WebSite 類來模擬一個網站的模板,全部類型能夠經過對 name 賦值而後調用 use 方法進行修改緩存
public class WebSite { private String name = ""; public WebSite(String name) { this.name = name; } public void use(){ System.out.println("當前網站分類: " + name); } }
若是按照剛纔的思路,是這樣操做的服務器
public class Test { public static void main(String[] args) { WebSite webSite1 = new WebSite("博客"); webSite1.use(); WebSite webSite2 = new WebSite("博客"); webSite2.use(); WebSite webSite3 = new WebSite("博客"); webSite3.use(); WebSite webSite4 = new WebSite("新聞發佈"); webSite4.use(); WebSite webSite5 = new WebSite("公衆號"); webSite5.use(); WebSite webSite6 = new WebSite("公衆號"); webSite6.use(); } }
運行結果:數據結構
當前網站分類: 博客
當前網站分類: 博客
當前網站分類: 博客
當前網站分類: 新聞發佈
當前網站分類: 公衆號
當前網站分類: 公衆號ide
① 假設虛擬空間在同一臺服務器上,作上述內容,須要實例化 6 個 WebSite,而其本質又沒有很大的差異,因此對於服務器的資源浪費很大測試
② 網站結構類似度很高,基本全是重複的代碼優化
對於這種重複性很高的內容,首先咱們要作到將其抽象出來,重複建立實例在設計模式中確定是不太明智的,咱們想要作到多個客戶,共享同一個實例。這樣不論是代碼仍是服務器資源利用,都會改善不少
一個不算特別恰當的例子:例如外賣平臺中的一個一個商家店鋪,是否是能夠理解爲平臺中的一個小店鋪,小網站,其中經過例如店鋪 ID 等內容來區分不一樣店鋪,可是其每一家店鋪總體的模板和樣子是差很少的。
咱們下面要作的就是,將大量類似內容抽象成一個網站模板類,而後把一些特定的內容,經過參數移到實例的外面,調用的時候再指定,這樣能夠大幅度減小單個實例的數目。
建立一個抽象的 WebSite 類
public abstract class WebSite { public abstract void use(); }
接下來是具體實現,建立其子類,和前面同樣,全部類型能夠經過對 type賦值而後調用 use 方法進行修改
public class ConcreteWebSite extends WebSite { // 網站發佈形式 private String type = ""; public ConcreteWebSite(String type) { this.type = type; } @Override public void use() { System.out.println("當前網站分類: " + type); } }
建立一個工廠類,用於建立,返回一個指定的網站實例
這一個類,首先用一個 HashMap 模擬一種鏈接池的概念,由於咱們既然想要達到不重複建立實例的效果,就須要經過一些邏輯判斷,判斷 Map 中是否存在這個實例,若是有就直接返回,若是沒有就建立一個新的,一樣類型 type 是在調用時,顯式的指定的。
後面補充了一個獲取網站分類總數的方法,用來測試的時候,看一下是否是沒有重複建立實例
import java.util.HashMap; /** * 網站工廠類,根據須要返回 */ public class WebSiteFactory { // 模擬一個鏈接池 private HashMap<String, ConcreteWebSite> pool = new HashMap<>(); /** * 獲取網站:根據傳入的類型,返回網站,無則建立,有則直接返回 * * @param type * @return */ public WebSite getWebSiteCategory(String type) { if (!pool.containsKey(type)) { // 建立一個網站,放到池種 pool.put(type, new ConcreteWebSite(type)); } return (WebSite) pool.get(type); } /** * 獲取網站分類總數 */ public int getWebSiteCount() { return pool.size(); } }
測試一下
public class Test { public static void main(String[] args) { // 建立一個工廠 WebSiteFactory factory = new WebSiteFactory(); // 給客戶建立一個博客類型的網站 WebSite webSite1 = factory.getWebSiteCategory("博客"); webSite1.use(); // 給客戶建立一個博客類型的網站 WebSite webSite2 = factory.getWebSiteCategory("博客"); webSite2.use(); // 給客戶建立一個博客類型的網站 WebSite webSite3 = factory.getWebSiteCategory("博客"); webSite3.use(); // 給客戶建立一個新聞發佈類型的網站 WebSite webSite4 = factory.getWebSiteCategory("新聞發佈"); webSite4.use(); // 給客戶建立一個公衆號類型的網站 WebSite webSite5 = factory.getWebSiteCategory("公衆號"); webSite5.use(); // 給客戶建立一個公衆號類型的網站 WebSite webSite6 = factory.getWebSiteCategory("公衆號"); webSite6.use(); // 查看一下鏈接池中的實例數 System.out.println("實例數:" + factory.getWebSiteCount()); } }
運行結果:
當前網站分類: 博客
當前網站分類: 博客
當前網站分類: 博客
當前網站分類: 新聞發佈
當前網站分類: 公衆號
當前網站分類: 公衆號
實例數:3
上面的代碼,使用工廠代替了直接實例化的方式,工廠中,主要經過一個池的概念,實現了共享對象的目的,可是其實咱們會發現,例如建立三個博客類型的網站,可是好像這三個網站就是如出一轍的,可是不一樣的客戶,其中博客網站中的數據確定是不一樣的,這就是咱們尚未區份內部外部的狀態
內部狀態:對象共享出來的信息,存儲在享元對象內部而且不會隨環境改變的共享部分
外部狀態:對象用來標記的一個內容,隨環境會改變,不可共享
打個比方,五子棋只有黑白兩色,總不能下多少子,就建立多少個實例吧,因此咱們把顏色看作內部狀態,有黑白兩種顏色。而各個棋子的位置並不相同,當咱們落子後這個位置信息纔會被傳入,因此位置信息就是外部狀態
那麼對於「外包網站」的例子中,很顯然,不一樣的客戶網站數據就是一個外部狀態,下面來修改一下
首先新增一個 User 類,後面會將其引入做爲外部狀態
public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } }
修改抽象類和子類,經過參數的方式引入 User 這個外部狀態
抽象類
public abstract class WebSite { public abstract void use(User user); }
子類
public 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()); } }
工廠類不變,最後修改測試類
public class Test { public static void main(String[] args) { // 建立一個工廠 WebSiteFactory factory = new WebSiteFactory(); // 給客戶建立一個博客類型的網站 WebSite webSite1 = factory.getWebSiteCategory("博客"); webSite1.use(new User("客戶A")); // 給客戶建立一個博客類型的網站 WebSite webSite2 = factory.getWebSiteCategory("博客"); webSite2.use(new User("客戶B")); // 給客戶建立一個博客類型的網站 WebSite webSite3 = factory.getWebSiteCategory("博客"); webSite3.use(new User("客戶C")); // 給客戶建立一個新聞發佈類型的網站 WebSite webSite4 = factory.getWebSiteCategory("新聞發佈"); webSite4.use(new User("客戶A")); // 給客戶建立一個公衆號類型的網站 WebSite webSite5 = factory.getWebSiteCategory("公衆號"); webSite5.use(new User("客戶A")); // 給客戶建立一個公衆號類型的網站 WebSite webSite6 = factory.getWebSiteCategory("公衆號"); webSite6.use(new User("客戶B")); // 查看一下鏈接池中的實例數 System.out.println("實例數:" + factory.getWebSiteCount()); } }
運行結果:
【網站分類】: 博客 【客戶】: 客戶A
【網站分類】: 博客 【客戶】: 客戶B
【網站分類】: 博客 【客戶】: 客戶C
【網站分類】: 新聞發佈 【客戶】: 客戶A
【網站分類】: 公衆號 【客戶】: 客戶A
【網站分類】: 公衆號 【客戶】: 客戶B
實例數:3
能夠看出來,雖然有 6 個客戶,可是實際上只有三個實例,一樣再增長几十個,也最多隻會有三個實例
定義:享元(Flyweight)模式運用共享技術來有效地支持大量細粒度對象的複用。
它經過共享已經存在的對象來大幅度減小須要建立的對象數量、避免大量類似類的開銷,從而提升系統資源的利用率。
享元模式又叫作蠅量模式,因此英文爲 Flyweight
注:方法參數和返回值沒細細弄,主要爲了說明結構
優勢:相同對象只須要保存一份,下降了系統中內存的數量,減小了系統內存的壓力
缺點:程序複雜性增大,同時讀取享元模式的外部狀態會使得運行時間稍微變長
享元模式其中也須要一個工廠進行控制,因此就好像是在工廠方法模式的基礎上,增長了一個緩存機制,也就是經過一個 「池」 的概念,避免了大量相同的對象建立,大大下降了內存空間的消耗。
那麼應用場景以下: