【設計模式】第十一篇:來一塊兒瞅瞅享元模式

今天一塊兒來看一個新的設計模式,那就是享元模式,關於此模式,常見的就是 「項目外包」、
以及 「五子棋」 這樣兩個例子,咱們下面就選擇使用 「項目外包」 這個例子引入去講java

一 故事引入

(一) 故事背景

程序員小B,幫助客戶 A 作了一個展現一些產品內容的網站,經過 A 的 推薦,客戶 B 、客戶C 也想要作這樣一個網站,可是就是形式有一些變化程序員

  • 有的客戶但願是新聞發佈形式的
  • 有的客戶但願是博客形式的
  • 有的客戶但願是公衆號形式的等等

並且他們都但願可以下降一些費用,可是每個空間部署着一個網站,因此租借空間的費用是固定的,同時程序員小B 並不想從本身的勞動報酬中縮減費用web

(二) 思考解決方案

(1) 最簡單的傳統方案

先說最簡單能想到的方案,直接把網站代碼複製幾份,而後每個都租借一個空間,而後對代碼進行定製修改。注:這裏還沒考慮優化或者省錢設計模式

咱們用一個 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

(2) 存在的問題及改進思路

  • ① 假設虛擬空間在同一臺服務器上,作上述內容,須要實例化 6 個 WebSite,而其本質又沒有很大的差異,因此對於服務器的資源浪費很大測試

  • ② 網站結構類似度很高,基本全是重複的代碼優化

對於這種重複性很高的內容,首先咱們要作到將其抽象出來,重複建立實例在設計模式中確定是不太明智的,咱們想要作到多個客戶,共享同一個實例。這樣不論是代碼仍是服務器資源利用,都會改善不少

一個不算特別恰當的例子:例如外賣平臺中的一個一個商家店鋪,是否是能夠理解爲平臺中的一個小店鋪,小網站,其中經過例如店鋪 ID 等內容來區分不一樣店鋪,可是其每一家店鋪總體的模板和樣子是差很少的。

咱們下面要作的就是,將大量類似內容抽象成一個網站模板類,而後把一些特定的內容,經過參數移到實例的外面,調用的時候再指定,這樣能夠大幅度減小單個實例的數目。

(3) 享元模式初步改進

建立一個抽象的 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

(4) 享元模式再改進-區份內外部狀態

上面的代碼,使用工廠代替了直接實例化的方式,工廠中,主要經過一個池的概念,實現了共享對象的目的,可是其實咱們會發現,例如建立三個博客類型的網站,可是好像這三個網站就是如出一轍的,可是不一樣的客戶,其中博客網站中的數據確定是不一樣的,這就是咱們尚未區份內部外部的狀態

內部狀態:對象共享出來的信息,存儲在享元對象內部而且不會隨環境改變的共享部分

外部狀態:對象用來標記的一個內容,隨環境會改變,不可共享

打個比方,五子棋只有黑白兩色,總不能下多少子,就建立多少個實例吧,因此咱們把顏色看作內部狀態,有黑白兩種顏色。而各個棋子的位置並不相同,當咱們落子後這個位置信息纔會被傳入,因此位置信息就是外部狀態

那麼對於「外包網站」的例子中,很顯然,不一樣的客戶網站數據就是一個外部狀態,下面來修改一下

首先新增一個 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

(二) 結構圖

注:方法參數和返回值沒細細弄,主要爲了說明結構

  • 抽象享元角色(Flyweight):是全部的具體享元類的超類或接口,非享元的外部狀態以參數的形式經過方法傳入。
  • 具體享元(Concrete Flyweight)角色:實現抽象享元角色中所規定的接口。
  • 非享元(Unsharable Flyweight) 角色:是不共享的外部狀態,它以參數的形式注入具體享元的相關方法中,這也意味着,享元模式並不強制共享
  • 享元工廠(Flyweight Factory)角色:負責建立和管理享元角色。
    • 當客戶對象請求一個享元對象時,享元工廠檢査系統中是否存在符合要求的享元對象
      • 若是存在則提供給客戶
      • 若是不存在的話,則建立一個新的享元對象

(二) 簡述優缺點

優勢:相同對象只須要保存一份,下降了系統中內存的數量,減小了系統內存的壓力

缺點:程序複雜性增大,同時讀取享元模式的外部狀態會使得運行時間稍微變長

(三) 應用場景

享元模式其中也須要一個工廠進行控制,因此就好像是在工廠方法模式的基礎上,增長了一個緩存機制,也就是經過一個 「池」 的概念,避免了大量相同的對象建立,大大下降了內存空間的消耗。

那麼應用場景以下:

  • 一個程序使用了大量類似或者相同的對象,且形成了很大的開銷的時候
  • 大部分對象,能夠根據內部狀態分組,且可將不一樣部分外部化,這樣每個組只需保存一個內部狀態。
    • 例如上面的博客,新聞,公衆號站形式就是三種組,每一個組只須要傳入用戶數據這個外部狀態便可
  • 由於使用享元模式,須要一個保存享元的數據結構(例如上面的 Hashmap)因此請確認實例足夠多的時候才值得去使用享元模式。
相關文章
相關標籤/搜索