天天看點

設計模式:享元模式解決重複對象的記憶體浪費問題

作者:日拱一卒程式猿

一、定義

“享元”指被共享的單元。享元模式就是通過複用對象,以達到節省記憶體的目的。

嘗試複用現有的同類對象,如果未找到比對的對象,則建立新對象。

意圖:運用共享技術有效地支援大量細粒度的對象。

主要解決:在有大量對象時,有可能會造成記憶體溢出,我們把其中共同的部分抽象出來,如果有相同的業務請求,直接傳回在記憶體中已有的對象,避免重新建立。

二、内部狀态和外部狀态

享元模式提出了兩個要求: 細粒度和共享對象。這裡涉及到内部狀态和外部狀态,即将對象的資訊分為兩個部分:内部狀态和外部狀态。

内部狀态指對象共享出來的資訊,存儲在享元對象内部且不會随環境的改變而改變

外部狀态指對象得以依賴的一個标記,是随環境改變而改變的、不可共享的狀态

以圍棋為例,圍棋隻有黑白二色,其顔色不會随環境改變而改變,是以圍棋顔色就是圍棋的内部狀态,可共享;棋子之間的差别在于位置不同,而落子後,棋子的位置會發生變化,是以棋子的坐标是棋子的外部狀态,不可共享。

三、享元模式原理類圖

設計模式:享元模式解決重複對象的記憶體浪費問題

(1)抽象享元角色(Flyweight):是所有的具體享元類的基類,為具體享元規範需要實作的公共接口,非享元的外部狀态以參數的形式通過方法傳入。

(2)具體享元角色(Concrete Flyweight):實作抽象享元角色中所規定的接口。

(3)非享元角色(Unsharable Flyweight) :是不可以共享的外部狀态,它以參數的形式注入具體享元的相關方法中。

(4)享元工廠(Flyweight Factory)角色:負責建立和管理享元角色。當客戶對象請求一個享元對象時,享元工廠檢查系統中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則建立一個新的享元對象。

四、案例

以網站項目展示為例,有的要求以新聞形式釋出,有的要求以部落格形式釋出,有的要求以微信公衆号形式釋出。

整合到一個網站中,共享其相關的代碼和資料,對于硬碟、記憶體、CPU、資料庫空間等伺服器資源都可以達成共享,減少伺服器資源。

設計模式:享元模式解決重複對象的記憶體浪費問題
//抽象享元角色
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 User {
    private String name;
 
    public User(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

//享元工廠
public class WebSiteFactory {
    //集合,充當池的作用
    private HashMap<String,ConcreteWebSite> pool = new HashMap<>();
 
    //根據網站類型,傳回一個網站,如果沒有就建立一個并放入池中
    public WebSite getWebSiteCategory(String type){
        if(!pool.containsKey(type)){
            pool.put(type,new ConcreteWebSite(type));
        }
        return pool.get(type);
    }
 
    //擷取網站分類數
    public int getWebSiteCount(){
        return pool.size();
    }
 
}

//用戶端
public class Client {
    public static void main(String[] args) {
        //建立一個工程類
        WebSiteFactory webSiteFactory = new WebSiteFactory();
 
        //A以一個新聞形式釋出網站
        WebSite newsWebsite = webSiteFactory.getWebSiteCategory("新聞");
        newsWebsite.use(new User("A"));
        //B以一個部落格形式釋出網站
        WebSite blogWebsite1 = webSiteFactory.getWebSiteCategory("部落格");
        blogWebsite1.use(new User("B"));
        //C以一個部落格形式釋出網站
        WebSite blogWebsite2 = webSiteFactory.getWebSiteCategory("部落格");
        blogWebsite2.use(new User("C"));
        //D以一個部落格形式釋出網站
        WebSite blogWebsite3 = webSiteFactory.getWebSiteCategory("部落格");
        blogWebsite3.use(new User("D"));
 
        //實際網站釋出分類執行個體
        System.out.println("實際網站釋出分類執行個體:"+webSiteFactory.getWebSiteCount());
 
    }
}
           

五、應用

在Java Integer的實作中,-128到127之間的整型對象會被事先建立好,緩存在IntegerCache類中。當我們使用自動裝箱或者valueOf()來建立這個數值區間的整型對象時,會複用IntegerCache類事先建立好的對象。這裡的IntegerCache類就是享元工廠類,事先建立好的整型對象就是享元對象。

在Java String類的實作中,JVM開辟一塊存儲區專門存儲字元串常量,這塊存儲區叫作字元串常量池,類似于Integer中的IntegerCache。不過,跟IntegerCache不同的是,它并非事先建立好需要共享的對象,而是在程式的運作期間,根據需要來建立和緩存字元串常量。

繼續閱讀