初識設計模式 – 享元模式

簡介

古代的活字印刷術就有點像享元模式,活字印刷就是將每個字模做出來,再印刷時再選取需要的字模到印刷板上,這樣就構成了一頁書的印刷板。這樣的活字印刷大大提升了效率,減少了印刷板的空間。

在享元模式中,存儲共享實例對象的地方稱為享元池(Flyweight Pool)。類比到上述的活字印刷術中,放置字模的地方就是享元池。

享元模式能做到共享的關鍵是區分了內部狀態和外部狀態,它們的簡單釋義是:

  • 內部狀態:存儲在享元對象內部且不跟隨環境變化而改變的狀態,內部狀態可以在對象之間共享
  • 外部狀態:通常由客戶端保存,當需要時再傳入享元對象中的狀態,會跟隨環境變化而改變,不可以在對象之間共享。

典型示例

一個典型的享元工廠類的程式碼示例如下:

public class FlyweightFactory {
    // 使用 HashMap 定義享元池
    private final HashMap<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        // 如果對象存在,直接從享元池中獲取
        if (flyweights.containsKey(key)) {
            return (Flyweight) flyweights.get(key);
        } else {
            // 如果對象不存在,先創建一個新的對象添加到享元池中,然後返回
            Flyweight fw = new ConcreteFlyweight();
            flyweights.put(key, fw);
            return fw;
        }
    }
}

一個典型的享元抽象類的程式碼示例如下:

public abstract class Flyweight {
    // 內部狀態作為成員對象,同一個享元對象其內部狀態是一致的
    protected Object intrinsicState;

    public Flyweight(Object intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    public void operation(Object extrinsicState) {
        // 外部狀態在使用時由外部提供,每一次都可以不同
    }
}

通常,實際會根據業務情況定義具體享元類,程式碼示例如下:

public class ConcreteFlyweight extends Flyweight {
    public ConcreteFlyweight(Object intrinsicState) {
        super(intrinsicState);
    }

    @Override
    public void operation(Object extrinsicState) {
        // 結合內部狀態 intrinsicState 以及方法參數傳入的 extrinsicState 完成具體邏輯
    }
}

總結

優點

享元模式的主要優點如下:

  • 極大地減少記憶體中對象的數量,節約了系統資源,提高了系統性能
  • 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元對象可以在不同的環境中被共享

缺點

享元模式的主要缺點如下:

  • 享元模式需要分離出內部狀態和外部狀態,從而使得系統變得複雜,這使得程式的邏輯複雜化
  • 為了使對象可以共享,享元對象需要將部分狀態外部化,而讀取外部狀態將使得運行時間變長

適用場景

享元模式的適用場景如下:

  • 一個系統有大量相同或相似的對象,造成記憶體大量耗費

源碼

在 Java 類庫中的 String 類就使用了享元模式,使用字面量創建的對象是共享的,而不會重新為此分配記憶體空間。