1. 程式人生 > 實用技巧 >享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)

1.模式動機

面向物件技術可以很好地解決一些靈活性或可擴充套件性問題,但在很多情況下需要在系統中增加類和物件的個數。當物件數量太多時,將導致執行代價過高,帶來效能下降等問題。

  • 享元模式正是為解決這一類問題而誕生的。享元模式通過共享技術實現相同或相似物件的重用。
  • 在享元模式中可以共享的相同內容稱為內部狀態(Intrinsic State)而那些需要外部環境來設定的不能共享的內容稱為外部狀態(Extrinsic State),由於區分了內部狀態和外部狀態,因此可以通過設定不同的外部狀態使得相同的物件可以具有一些不同的特徵,而相同的內部狀態是可以共享的。
  • 在享元模式中通常會出現工廠模式,需要建立一個享元工廠來負責維護一個享元池(Flyweight Pool)用於儲存具有相同內部狀態的享元物件。
  • 在享元模式中共享的是享元物件的內部狀態,外部狀態需要通過環境來設定。在實際使用中,能夠共享的內部狀態是有限的,因此享元物件一般都設計為較小的物件,它所包含的內部狀態較少,這種物件也稱為細粒度物件。享元模式的目的就是使用共享技術來實現大量細粒度物件的複用。

2.模式定義

享元模式(Flyweight Pattern):運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用。由於享元模式要求能夠共享的物件必須是細粒度物件,因此它又稱為輕量級模式,它是一種物件結構型模式。

3.模式結構

享元模式包含如下角色:

  • Flyweight: 抽象享元類
  • ConcreteFlyweight: 具體享元類
  • UnsharedConcreteFlyweight: 非共享具體享元類
  • FlyweightFactory: 享元工廠類

4.時序圖

5.程式碼分析

public interface Flyweight {
    //一個示意性方法,引數state是外部狀態
    public void operation(String state);
}

public class ConcreteFlyweight implements Flyweight {
    private Character intrinsicState = null;
    /**
     * 建構函式,內部狀態作為引數傳入
     * @param state
     */
    public ConcreteFlyweight(Character state){
        this.intrinsicState = state;
    }
    
    
    /**
     * 外部狀態作為引數傳入方法中,改變方法的行為,
     * 但是並不改變物件的內蘊狀態。
     */
    @Override
    public void operation(String state) {
        // TODO Auto-generated method stub
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }

}

public class FlyweightFactory {
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
    
    public Flyweight factory(Character state){
        //先從快取中查詢物件
        Flyweight fly = files.get(state);
        if(fly == null){
            //如果物件不存在則建立一個新的Flyweight物件
            fly = new ConcreteFlyweight(state);
            //把這個新的Flyweight物件新增到快取中
            files.put(state, fly);
        }
        return fly;
    }
}

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight fly = factory.factory(new Character('a'));
        fly.operation("First Call");
        
        fly = factory.factory(new Character('b'));
        fly.operation("Second Call");
        
        fly = factory.factory(new Character('a'));
        fly.operation("Third Call");
    }

}

6.模式分析

享元模式是一個考慮系統性能的設計模式,通過使用享元模式可以節約記憶體空間,提高系統的效能。

享元模式的核心在於享元工廠類,享元工廠類的作用在於提供一個用於儲存享元物件的享元池,使用者需要物件時,首先從享元池中獲取,如果享元池中不存在,則建立一個新的享元物件返回給使用者,並在享元池中儲存該新增物件。

享元模式以共享的方式高效地支援大量的細粒度物件,享元物件能做到共享的關鍵是區分內部狀態(Internal State)和外部狀態(External State)。

  • 內部狀態是儲存在享元物件內部並且不會隨環境改變而改變的狀態,因此內部狀態可以共享。
  • 外部狀態是隨環境改變而改變的、不可以共享的狀態。享元物件的外部狀態必須由客戶端儲存,並在享元物件被建立之後,在需要使用的時候再傳入到享元物件內部。一個外部狀態與另一個外部狀態之間是相互獨立的。

7.享元模式的優缺點

優點:
  • 享元模式的優點在於它可以極大減少記憶體中物件的數量,使得相同物件或相似物件在記憶體中只儲存一份。
  • 享元模式的外部狀態相對獨立,而且不會影響其內部狀態,從而使得享元物件可以在不同的環境中被共享。
缺點:
  • 享元模式使得系統更加複雜,需要分離出內部狀態和外部狀態,這使得程式的邏輯複雜化。
  • 為了使物件可以共享,享元模式需要將享元物件的狀態外部化,而讀取外部狀態使得執行時間變長。
適用環境:
  • 一個系統有大量相同或者相似的物件,由於這類物件的大量使用,造成記憶體的大量耗費。
  • 物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。
  • 使用享元模式需要維護一個儲存享元物件的享元池,而這需要耗費資源,因此,應當在多次重複使用享元物件時才值得使用享元模式。

8.模式應用

享元模式在編輯器軟體中大量使用,如在一個文件中多次出現相同的圖片,則只需要建立一個圖片物件,通過在應用程式中設定該圖片出現的位置,可以實現該圖片在不同地方多次重複顯示。

9.模式擴充套件

單純享元模式和複合享元模式

  • 單純享元模式:在單純享元模式中,所有的享元物件都是可以共享的,即所有抽象享元類的子類都可共享,不存在非共享具體享元類。
  • 複合享元模式:將一些單純享元使用組合模式加以組合,可以形成複合享元物件,這樣的複合享元物件本身不能共享,但是它們可以分解成單純享元物件,而後者則可以共享。

享元模式與其他模式的聯用

  • 在享元模式的享元工廠類中通常提供一個靜態的工廠方法用於返回享元物件,使用簡單工廠模式來生成享元物件。
  • 在一個系統中,通常只有唯一一個享元工廠,因此享元工廠類可以使用單例模式進行設計
  • 享元模式可以結合組合模式形成複合享元模式,統一對享元物件設定外部狀態。

10.總結

  • 享元模式運用共享技術有效地支援大量細粒度物件的複用。系統只使用少量的物件,而這些物件都很相似,狀態變化很小,可以實現物件的多次複用,它是一種物件結構型模式。
  • 享元模式包含四個角色:抽象享元類宣告一個介面,通過它可以接受並作用於外部狀態;具體享元類實現了抽象享元介面,其例項稱為享元物件;非共享具體享元是不能被共享的抽象享元類的子類;享元工廠類用於建立並管理享元物件,它針對抽象享元類程式設計,將各種型別的具體享元物件儲存在一個享元池中。
  • 享元模式以共享的方式高效地支援大量的細粒度物件,享元物件能做到共享的關鍵是區分內部狀態和外部狀態。其中內部狀態是儲存在享元物件內部並且不會隨環境改變而改變的狀態,因此內部狀態可以共享;外部狀態是隨環境改變而改變的、不可以共享的狀態。
  • 享元模式主要優點在於它可以極大減少記憶體中物件的數量,使得相同物件或相似物件在記憶體中只儲存一份;其缺點是使得系統更加複雜,並且需要將享元物件的狀態外部化,而讀取外部狀態使得執行時間變長。
  • 享元模式適用情況包括:一個系統有大量相同或者相似的物件,由於這類物件的大量使用,造成記憶體的大量耗費;物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中;多次重複使用享元物件。