1.定義

使用共享物件可有效的支援大量的細粒度的物件。

享元模式是池技術的重要實現方式,享元模式的定義為我們提出了兩個要求,細粒度物件和共享物件。我們知道分配太多的物件到以程式中將有損程式的效能,還會造成記憶體溢位,享元模式正是為此而生的。
說到細粒度物件,不可避免地使得物件數量多且性質相近,可以將物件資訊分為兩個部分:內部狀態(intrinic)與外部狀態(extrinisic)

  • 內部狀態

    物件可以共享出來的資訊,儲存在物件內部並不會隨著環境的改變而改變,它們可以作為一個物件的動態附加資訊,不必直接儲存在具體某個物件中,屬於可以共享的部分。
  • 外部狀態

    是物件得以依賴的一個標記,是隨環境改變而改變的、不可以共享的狀態。

2.類圖

角色介紹

  • Flyweight 抽象享元角色:一個產品的抽象類,同時定義出物件的內部狀態和外部狀態的介面或實現。
  • ConcreteFlyweight 具體享元角色:具體的一個產品類,實現抽象角色定義的業務。該角色中需要注意的是內部狀態處理應該與環境無關,不應該出現一個操作改變了內部狀態,同時改變了外部狀態,絕對不允許。
  • unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如執行緒安全)不能夠使用共享技術的物件,該物件一般不會出現在享元工廠中。
  • FlyweightFactory 享元工廠:構造一個池容器,同時提供從池中獲得物件的方法

3.通用原始碼

抽象享元角色

public abstract class Flyweight {
    //內部狀態
    private String intrinsic;
    //外部狀態
    protected final String Extrinsic;
    //要求享元角色必須接受外部狀態
    public Flyweight (String _Extrinsic) {
        this.Extrinsic = _Extrinsic;
    }
    //定義業務操作
    public abstract void operate();
    //內部狀態的getter/setter
    public String getIntrinsic() {
        return intrinsic;
    }
    public void setIntrinsic(String intrinsic) {
        this.intrinsic = intrinsic;
    }
}

具體享元模式

public class ConcreteFlyweight1 extends Flyweight {
    //接受外部狀態
    public ConcreteFlyweight1(String _Extrinsic) {
        super(_Extrinsic);
    }
    //根據外部狀態進行邏輯處理
    public void operate() {
          //業務邏輯
    }
}
public class ConcreteFlyweight2 extends Flyweight {
    //接受外部狀態
    public ConcreteFlyweight2(String _Extrinsic) {
        super(_Extrinsic);
    }
    //根據外部狀態進行邏輯處理
    public void operate() {
          //業務邏輯
    }
}

享元工廠

public class FlyweightFactory {
    //定義一個池容器
    private static HashMap<String,Flyweight> pool = new HashMap<String,Flyweight>();
    //享元工廠
    public static Flyweight getFlyweight(String Extrinsic) {
        //要返回的物件
        Flyweight flyweight = null;
        //在池中沒有該物件
        if(pool.containsKey(Extrinsic)) {
            flyweight = pool.get(Extrinsic);
        }else {
            //根據外部狀態建立享元物件
            flyweight = new ConcreteFlyweight1(Extrinsic);
        }
        return flyweight;
    }
}

4.Demo

假設現在有一個報考系統,有一個模組負責社會人員報名,該模組只開放三天,並限制報考人員的數量,兩天系統宕了4次。原因是每個人報名都會建立一個物件,物件太多,導致記憶體耗盡,現在用享元模式,優化。
報考資訊類

public class SignInfo {
    private String id; // 報名人員的id
    private String location; // 考試地點
    private String subject; // 考試科目
    private String postAddress; // 郵寄地址

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getPostAddress() {
        return postAddress;
    }

    public void setPostAddress(String postAddress) {
        this.postAddress = postAddress;
    }

}

帶物件池的報考資訊

public class SignInfo4Pool extends SignInfo {

    //定義一個物件池提取的key值
    private String key;
    //建構函式獲得相同標誌
    public SignInfo4Pool(String _key) {
        this.key = _key;
    }
    public String getKey() {
        return key;
    }
    public void setKey(String key) {
        this.key = key;
    }
}

帶物件池的工廠類

public class SignInfoFactory {
   //池容器
    private static HashMap<String,SignInfo> pool = new HashMap<String,SignInfo>();
    //報名資訊工廠
    @Deprecated
    public static SignInfo getSignInfo() {
        return new SignInfo();
    }

    //從池中獲取物件
    public static SignInfo getSignInfo(String key) {
        SignInfo result = null; //設定返回物件
        //池中沒有該物件,則建立,放入池中
        if(!pool.containsKey(key)) {
            System.out.println(key + "---建立物件,並放入到池中");
            result = new SignInfo4Pool(key);
            pool.put(key, result);
        }else {
            result = pool.get(key);
            System.out.println(key + "---直接從池中取得");
        }
        return result;
    }
}

場景類

public class Client {
    public static void main(String[] args) {
        //初始化物件池
        for (int i = 0; i < 4; i++) {
            String subject = "科目"+i;
            //初始化地址
            for (int j = 0; j <30; j++) {
                String key = subject + "考試地點" + j;
                SignInfoFactory.getSignInfo(key);
            }
        }

        SignInfo signInfo = SignInfoFactory.getSignInfo("科目1考試地點1");
    }
}

執行結果:
這裡寫圖片描述
最後一個物件,直接從容器裡取的,這個物件是帶有科目和考試地點,之後每建一個物件都會享有科目和考試資訊。

5.應用場景

  • 系統中存在大量相似的物件
  • 細粒度的物件都具備較接近的外部狀態,而且內部狀態與環境無關,也就是說物件沒有特定身份
  • 需要緩衝池

6.優缺點

  • 優點
    • 減少應用程式建立物件,降低程式記憶體的佔用
    • 增強程式的效能
  • 缺點
    • 提高了系統複雜性
    • 需要分離出外部狀態和內部狀態,而且外部狀態具有固定化性,不應隨內部狀態的改變而改變,否則會導致系統的邏輯混亂。



參考資料
01. 秦曉波.《設計模式之禪》. 機械工業出版社 :2010年