1. 程式人生 > 其它 >05-06-設計模式 享元模式

05-06-設計模式 享元模式

展示網站專案需求

小型的外包專案, 給客戶A做一個產品展示網站, 客戶A的朋友感覺效果很不錯, 也需要做這樣的產品網站, 但是要求都有些不同

  1. 有客戶要求以新聞的方式釋出
  2. 有客戶要求以部落格的形式釋出
  3. 有客戶希望以微信公眾號的形式釋出

傳統解決方案

  1. 直接複製貼上一份, 然後根據客戶不同的要求, 進行定製修改(之前我待過的一家公司,也是這樣做的)
  2. 給每個網站租用一個空間

傳統方案問題分析

  1. 需要的網站結構相似度很高,而且都不是高訪問量網站,如果分成多個虛擬空間來處理,相當於一個相同網站的例項物件很多,造成伺服器的資源浪費
  2. 解決思路:整合到一個網站中,共享其相關的程式碼和資料,對於硬碟、記憶體、CPU、資料庫空間等伺服器資源都可以達成共享,減少伺服器資源
  3. 對於程式碼來說,由於是一份例項,維護和擴充套件都更加容易
  4. 上面的解決思路就可以使用享元模式來解決

享元模式

基本介紹

  1. 享元模式(FlyweightPattern)也叫蠅量模式:運用共享技術有效地支援大量細粒度的物件
  2. 常用於系統底層開發,解決系統的效能問題。像資料庫連線池,裡面都是建立好的連線物件,在這些連線物件中有我們需要的則直接拿來用,避免重新建立,如果沒有我們需要的,則建立一個
  3. 享元模式能夠解決重複物件的記憶體浪費的問題,當系統中有大量相似物件,需要緩衝池時。不需總是建立新物件,可以從緩衝池裡拿。這樣可以降低系統記憶體,同時提高效率
  4. 享元模式經典的應用場景就是池技術了,String常量池、資料庫連線池、緩衝池等等都是享元模式的應用,享元模式是池技術的重要實現方式

享元模式的原理類圖

對類圖的說明對原理圖的說明-即(模式的角色及職責)

  1. FlyWeight是抽象的享元角色,他是產品的抽象類,同時定義出物件的外部狀態和內部狀態(後面介紹)的介面或實現
  2. ConcreteFlyWeight是具體的享元角色,是具體的產品類,實現抽象角色定義相關業務
  3. UnSharedConcreteFlyWeight是不可共享的角色,一般不會出現在享元工廠。
  4. FlyWeightFactory享元工廠類,用於構建一個池容器(集合),同時提供從池中獲取物件方法

內部狀態和外部狀態

比如圍棋、五子棋、跳棋,它們都有大量的棋子物件,圍棋和五子棋只有黑白兩色,跳棋顏色多一點,所以棋子顏色就是棋子的內部狀態;而各個棋子之間的差別就是位置的不同,當我們落子後,落子顏色是定的,但位置是變化的,所以棋子座標就是棋子的外部狀態

  1. 享元模式提出了兩個要求:細粒度和共享物件。這裡就涉及到內部狀態和外部狀態了,即將物件的資訊分為兩個部分:內部狀態和外部狀態
  2. 內部狀態指物件共享出來的資訊,儲存在享元物件內部且不會隨環境的改變而改變
  3. 外部狀態指物件得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
  4. 舉個例子:圍棋理論上有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原始碼

享元模式的注意事項和細節

其實我感覺這個模式的舉例並不好, 應為在真正的開發中, 不可能, 我多個客戶, 你給人家部署一套程式碼, 但是這個模式的思想還是很好的

  1. 在享元模式這樣理解,“享”就表示共享,“元”表示物件
  2. 系統中有大量物件,這些物件消耗大量記憶體,並且物件的狀態大部分可以外部化時,我們就可以考慮選用享元模式
  3. 用唯一標識碼判斷,如果在記憶體中有,則返回這個唯一標識碼所標識的物件,用HashMap/HashTable儲存
  4. 享元模式大大減少了物件的建立,降低了程式記憶體的佔用,提高效率
  5. 享元模式提高了系統的複雜度。需要分離出內部狀態和外部狀態,而外部狀態具有固化特性,不應該隨著內部狀態的改變而改變,這是我們使用享元模式需要注意的地方.
  6. 使用享元模式時,注意劃分內部狀態和外部狀態,並且需要有一個工廠類加以控制。
  7. 享元模式經典的應用場景是需要緩衝池的場景,比如String常量池、資料庫連線池