享元模式 FlyWeight 結構型 設計模式(十五)

享元模式(FlyWeight) 
」取「共享」之意,「」取「單元」之意。
image_5c087c0a_b27

意圖

運用共享技術,有效的支持大量細粒度的對象。

意圖解析

面向對象的程序設計中,一切皆是對象,這也就意味着系統的運行將會依賴大量的對象。
試想,若是對象的數量過多,勢必會增長系統負擔,致使運行的代價太高。
下面看兩個小例子理解下
1.)有一首歌曲叫作《大舌頭》
其中有一句歌詞「說說說說 說你愛我 我我我我 說不出口」
image_5c087c0a_3c7e
若是使用面向對象的編程方式對這段歌詞進行描述,假設一個漢字表示一個對象,你會怎麼作?
你會用七個仍是十六個對象進行表示?
image_5c087c0a_1241
 
2.)有一個文本編輯器軟件,對於每個字符使用對象進行表示
當打開一篇有不少重複字符的、數萬字的文章時,你會使用幾個對象進行表示?
若是仍舊採用每一個字符佔用一個對象,系統勢必崩潰,必然須要共享對象
 
上面的兩個例子中,都涉及到重複對象的概念 
而享元模式的意圖就是如此,將 重複的對象進行共享以達到支持大量細粒度對象的目的
若是不進行共享,如例2中描述的那樣,一篇數萬字符的文章將會產生數萬個對象,這將是一場可怕的災難。
image_5c087c0a_7394
flyweight意爲輕量級
在咱們當前的場景下,寓意爲經過共享技術,輕量級的---也就是內存佔用更小
本質就是「共享」因此中文翻譯過來多稱之爲享元
簡言之,享元模式就是要「共享對象」
對於Java語言,咱們熟悉的String,就是享元模式的運用
String是不可變對象,一旦建立,將不會改變
在JVM內部,String對象都是共享的
若是一個系統中的兩個String對象,包含的字符串相同,只會建立一個String對象提供給兩個引用
從而實現String對象的共享(new 的對象是兩個不一樣的)
image_5c087c0a_149c
享元模式又不只僅是簡單的「共享對象」
上面的兩個小例子中,對於文字中的重複字符
能夠經過共享對象的方式,對某些對象進行共享,從而減小內存開銷。
考慮下圖中的情景,這裏面全部的「你」字,究竟是不是一樣的?
  • 是,由於所有都是漢字「你」
  • 不是,由於儘管都是漢字「你」,可是他們的字體,顏色,字號,卻又明顯不一樣,因此不是一樣的
image_5c087c0b_388b
 
若是將字體、顏色、字號,做爲「你」這個漢字的狀態
是否是能夠認爲:他們都是同樣的漢字,可是他們卻又具備不一樣的狀態?
其實享元模式不只僅用來解決大量重複對象的共享問題,還可以用來解決類似對象的問題。
享元對象可以共享的關鍵在於:區分對象的 內部狀態外部狀態
內部狀態是存儲在享元對象內部的,而且不會隨環境的變化而有所改變。
好比上面的漢字「你」,不管在任何狀況下,漢字「你」,始終是「你」,不會變成「她」
因此說享元模式解決共享問題,本質是共享內部狀態
外部狀態是隨外部環境變化而變化,不能共享的狀態。
享元對象的外部狀態一般由客戶端保存,在必要的時候在傳遞到享元對象內部
好比上面漢字「你」的字體、顏色、字號就是外部狀態。   

小結

享元模式就是爲了不繫統中出現大量相同或類似的對象,同時又不影響客戶端程序經過面向對象的方式對這些對象進行操做
享元模式經過共享技術,實現相同或類似對象的重用
好比文編編輯器讀取文本
在邏輯上每個出現的字符都有一個對象與之對應,然而在物理上它們卻共享同一個享元對象
在享元模式中,存儲這些共享實例對象的地方一般叫作享元池(Flyweight  Pool)
享元模式能夠結合String的 intern()方法一塊兒進行理解
 
經過區分了內部狀態和外部狀態,就能夠將相同內部狀態的對象存儲在池中,池中的對象能夠實現共享
須要的時候將對象從池中取出,實現對象的複用
經過向取出的對象注入不一樣的外部狀態,進而獲得一些列類似的對象
而這些看似各異的對象在內存中,僅僅存儲了一份,大大節省了空間,因此說很天然的命名爲「flyweight」輕量級

享元工廠

經過對意圖的認識,能夠認爲, 享元模式其實就是對於「程序中會出現的大量重複或者類似對象」的一種「重構」
固然,你應該是在設計之初就想到這個問題,而不是真的出現問題後再去真的重構
好比,你想要設計「字符」這種對象時,就應該考慮到他的「大量」」重複「「類似」的特色
因此須要分析出字符的內部狀態,與外部狀態
上面也提到對於享元對象,經過享元池進行管理
對於池的管理一般使用工廠模式,藉助於工廠類對享元池進行管理
用戶須要對象時,經過工廠類獲取
工廠提供一個存儲在享元池中的已建立的對象實例,或者建立一個新的實例

示例代碼

針對於上面的例子,漢字「你」做爲內部狀態,能夠進行共享
「顏色」做爲外部狀態,由客戶端保存傳遞
建立字符類 Character、漢字字符類ChineseCharacter、顏色類Color以及工廠類CharacterFactory
Color含有顏色屬性,經過構造方法設置,getter方法獲取
package flyweight;
public class Color {
    public String Color;
    public Color(String color) {
        this.Color = color;
    }
    public String getColor() {
        return Color;
    }
}

 

Character 抽象的字符類,用於描述字符
package flyweight;
public abstract class Character {
    public abstract String getValue();
     
    public void display(Color color) {
        System.out.println("字符: " + getValue() + " ,顏色: " + color.getColor());
    }
}
漢字字符類,爲了簡化,直接設置value爲漢字「你」

 

package flyweight;
public class ChineseCharacter extends Character {
    @Override
    public String getValue() {
        return "你";
    }
}
CharacterFactory字符工廠類
經過單例模式建立工廠
內部HashMap用於存儲字符,而且提供獲取方法
爲了簡化程序,初始就建立了一個漢字字符「你」存儲於字符中
package flyweight;
import java.util.HashMap;
public class CharacterFactory {
    /**
    * 單例模式 餓漢式建立
    */
    private static CharacterFactory instance = new CharacterFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
    private CharacterFactory() {
        Character character = new ChineseCharacter();
        hm.put("你", character);
    }
    /**
    * 單例全局訪問接口獲取工廠
    */
    public static CharacterFactory getInstance() {
        return instance;
    }
     
    /**
    * 根據key獲取池中的對象
    */
    public Character getCharacter(String key) {
        return (Character) hm.get(key);
    }
}
測試代碼
image_5c087c0b_6037
示例中,咱們經過工廠,從享元池中獲取了三個漢字字符「你」。
經過 == 能夠看得出來,他們都是同一個對象
在分別調用他們的display方法時,在客戶端(此處爲咱們的Test main方法)中建立,而且傳遞給享元對象
經過方法參數的形式進行外部狀態的設置。
image_5c087c0b_2137
 
CharacterFactory 單例模式,返回自身實例
CharacterFactory內部維護Character的享元池
Character 依賴Color
ChineseCharacter是Character的實現類

結構

將上面的示例轉換爲標準的享元模式的名稱
image_5c087c0b_1edf
 
抽象享元角色 FlyWeight
全部具體享元類的超類,爲這些類規定了須要實現的公共接口
外部狀態能夠經過業務邏輯方法的參數形式傳遞進來
具體享元角色ConcreteFlyWeight
實現抽象享元角色所規定的的接口
須要保存內部狀態,並且,內部狀態必須與外部狀態無關
從而才能使享元對象能夠在系統內共享
享元工廠角色 FlyWeightFactory
負責建立和管理享元角色,也就是維護享元池
必須保證享元對象能夠被系統適當的共享
接受客戶端的請求
若是有適當符合要求的享元對象,則返回
若是沒有一個適當的享元對象,則建立
客戶端角色Client
客戶端角色維護了對全部享元對象的引用
image_5c087c0b_69dd
須要保存維護享元對象的外部狀態,而後經過享元對象的業務邏輯方法做爲參數形式傳遞
image_5c087c0b_6b32

分類

單純享元模式

在上面的結構中,若是全部的ConcreteFlyWeight均可以被共享
也就是全部的FlyWeight子類均可以被共享,那就是全部的享元對象均可以被共享
這種形式被稱之爲 單純享元模式
單純享元代碼
package flyweight.simple;
public abstract class FlyWeight {
/**
* 抽象的業務邏輯方法,接受外部狀態做爲參數
*/
abstract public void operation(String outerState);
}
package flyweight.simple;
public class ConcreteFlyWeight extends FlyWeight {
    private String innerState = null; 
    public ConcreteFlyWeight(String innerState) {
        this.innerState = innerState;
    } 
    /**
    * 外部狀態做爲參數傳遞
    */
    @Override
    public void operation(String outerState) {
        System.out.println("innerState = " + innerState + " outerState = " + outerState);
    }
}
package flyweight.simple;
import java.util.HashMap;
public class FlyWeightFactory {
    /**
    * 單例模式 餓漢式建立
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
    /**
    * 單例全局訪問接口獲取工廠
    */
    public static FlyWeightFactory getInstance() {
    return instance;
    }
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在建立並返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
        return (FlyWeight) hm.get(innerState);
        }else{
        FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
        hm.put(innerState,flyWeight);
        return flyWeight;
        }
    }
}

 

package flyweight.simple;
public class Test {
public static void main(String[] args){
FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");

System.out.println(flyWeight1);
System.out.println(flyWeight2);
System.out.println(flyWeight3);
System.out.println();

flyWeight1.operation("outer state XXX");
flyWeight2.operation("outer state YYY");
flyWeight3.operation("outer state ZZZ");
}
}

 

 
image_5c087c0b_75b6   

複合享元模式

與單純享元模式對應的是複合享元模式
單純享元模式中,全部的享元對象均可以共享
複合享元模式中,則並非全部的ConcreteFlyWeight均可以被共享
也就是說: 不是全部的享元對象均可以被共享
實際上,並非全部的FlyWeight子類都須要被共享
FlyWeight接口使的能夠進行共享,可是沒有任何須要 強制必須共享
實踐中,UnsharedConcreteFlyWeight對象一般將ConcreteFlyWeight對象做爲子節點
image_5c087c0b_4a4f
與單純享元模式相比,僅僅是擁有了不可共享的具體子類
並且,這個子類每每是應用了組合模式,將ConcreteFlyWeight對象做爲子節點
複合享元角色UnsharedConcreteFlyWeight
複合享元角色,也就是不可共享的,也被稱爲 不可共享的享元對象
可是一個複合享元對象能夠分解爲多個自己是單純享元對象的組合
這些單純的享元對象就又是能夠共享的
 
複合享元代碼
將簡單模式中的示例代碼進行改造
FlyWeight不變
package flyweight.composite;
public abstract class FlyWeight {
/**
* 抽象的業務邏輯方法,接受外部狀態做爲參數
*/
abstract public void operation(String outerState);
}
ConcreteFlyWeight不變
package flyweight.composite;
public class ConcreteFlyWeight extends FlyWeight {
private String innerState = null;
public ConcreteFlyWeight(String innerState) {
this.innerState = innerState;
}
/**
* 外部狀態做爲參數傳遞
*/
@Override
public void operation(String outerState) {
System.out.println("innerState = " + innerState + " outerState = " + outerState);
}
}
新增長不共享的子類也就是組合的享元子類
內部使用list 維護單純享元模式對象,提供add方法進行添加
提供operation操做
package flyweight.composite;
import java.util.ArrayList;
import java.util.List;
public class UnsharedConcreateFlyWeight extends FlyWeight {
private String innerState = null;
public UnsharedConcreateFlyWeight(String innerState) {
this.innerState = innerState;
}

private List<FlyWeight> list = new ArrayList<>();
public void add(FlyWeight flyWeight) {
list.add(flyWeight);
}
@Override
public void operation(String outerState) {
for (FlyWeight flyWeight:list) {
flyWeight.operation(outerState);
    }
}
}
FlyWeightFactory工廠類進行改造
新增長public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) 
用於得到組合享元對象
package flyweight.composite;
 
import java.util.HashMap;
 
public class FlyWeightFactory {
    /**
    * 單例模式 餓漢式建立
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
     
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    /**
    * 管理複合享元對象
    */
    private HashMap<String, Object> compositeHm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
     
    /**
    * 單例全局訪問接口獲取工廠
    */
    public static FlyWeightFactory getInstance() {
        return instance;
    }
     
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在建立並返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
            return (FlyWeight) hm.get(innerState);
        }else{
            FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
            hm.put(innerState,flyWeight);
            return flyWeight;
        }
    }
     
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在建立並返回
    */
    public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) {
        if(compositeHm.containsKey(state)){
            return (UnsharedConcreateFlyWeight) compositeHm.get(state);
        }else{
            UnsharedConcreateFlyWeight flyWeight = new UnsharedConcreateFlyWeight(state);
            compositeHm.put(state,flyWeight);
            return flyWeight;
        }
    }
 
}
測試類也進行改造
package flyweight.composite;
public class Test {
public static void main(String[] args){
    FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
    FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
    FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
    FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");
     
    System.out.println(flyWeight1);
    System.out.println(flyWeight2);
    System.out.println(flyWeight3);
     
    System.out.println("###########################################");
     
    flyWeight1.operation("outer state XXX");
    flyWeight2.operation("outer state YYY");
    flyWeight3.operation("outer state ZZZ");
    System.out.println("###########################################");
    UnsharedConcreateFlyWeight compositeFlyWeight = flyWeightFactory.getCompositeFylWeight("composite");
    compositeFlyWeight.add(flyWeight1);
    compositeFlyWeight.add(flyWeight2);
    compositeFlyWeight.operation("composite out state OOO");
    }
}
image_5c087c0b_5108
 
測試程序在原來的基礎上,新得到了一個組合享元對象
而後將兩個單純享元對象添加到組合享元對象中
而後調用operation,經過打印信息能夠看得出來
不一樣的單純享元對象,他們卻有了一致的外部狀態
image_5c087c0b_4c7c
 
因此使用複合享元模式的一個經常使用目的就是:
多個內部狀態不一樣的單純享元對象,擁有一致的外部狀態
這種場景下,就能夠考慮使用複合享元模式

使用場景

若是有下列狀況,則能夠考慮使用享元模式
  • 應用程序中使用了大量的對象
  • 大量的對象明顯增長了程序的存儲運行開銷
  • 對象能夠提取出內部狀態,而且能夠分離外部狀態
使用享元模式有一點須要特別注意:應用程序運行不依賴這些對象的身份
換句話說這些對象是不作區分的,適用於「在客戶端眼裏,他們都是同樣的」這種場景
好比單純的使用對象的方法,而不在乎對象是不是建立而來的,不然若是客戶端鑑別對象的身份(equals),當他們是同一個對象時將會出現問題  

總結

享元模式的核心就是共享
共享就須要找準內部狀態,以及分離外部狀態外部狀態由客戶端維護,在必要時候,經過參數的形式注入到享元對象中
在有大量重複或者類似對象的場景下,均可以考慮到享元模式
 
並且爲了達到共享的目的,須要經過 工廠對象進行控制
只有經過工廠來維護享元池才能達到共享的目的,若是任意建立使用則勢必不能很好地共享
 
享元模式大大的減小了對象的建立,下降了系統所須要的內存空間
可是因爲 將狀態分爲內部狀態和外部狀態,而外部狀態是分離的,那麼狀態的讀取必然會增大開銷
因此說 享元模式是時間換空間
 
若是肯定須要使用享元模式,若是對於多個內部狀態不一樣的享元對象,但願他們擁有一致性的外部狀態
那麼就能夠考慮複合享元模式,複合享元模式是與合成模式的結合。
 
相關文章
相關標籤/搜索