《JAVA與模式》之享元模式

《JAVA與模式》之享元模式

在閻宏博士的《JAVA與模式》一書中開頭是這樣描述享元(Flyweight)模式的:

  Flyweight在拳擊比賽中指最輕量級,即“蠅量級”或“雨量級”,這裏選擇使用“享元模式”的意譯,是因爲這樣更能反映模式的用意。享元模式是對象的結構模式。享元模式以共享的方式高效地支持大量的細粒度對象。


Java中的String類型

  在JAVA語言中,String類型就是使用了享元模式。String對象是final類型,對象一旦創建就不可改變。在JAVA中字符串常量都是存在常量池中的,JAVA會確保一個字符串常量在常量池中只有一個拷貝。String a="abc",其中"abc"就是一個字符串常量。

複製代碼
public class Test {

    public static void main(String[] args) {
        
        String a = "abc";
        String b = "abc";
        System.out.println(a==b);
        
    }
}
複製代碼

  上面的例子中結果爲:true ,這就說明a和b兩個引用都指向了常量池中的同一個字符串常量"abc"。這樣的設計避免了在創建N多相同對象時所產生的不必要的大量的資源消耗。

 

享元模式的結構

  享元模式採用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是內存的損耗。享元對象能做到共享的關鍵是區分內蘊狀態(Internal State)外蘊狀態(External State)。

  一個內蘊狀態是存儲在享元對象內部的,並且是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態並可以共享。

  一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元對象的外蘊狀態必須由客戶端保存,並在享元對象被創建之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。

  享元模式可以分成單純享元模式複合享元模式兩種形式。

單純享元模式  

  在單純的享元模式中,所有的享元對象都是可以共享的。

  單純享元模式所涉及到的角色如下:

  ●  抽象享元(Flyweight)角色 :給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。

  ●  具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。

  ●  享元工廠(FlyweightFactory)角色 :本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。

源代碼

  抽象享元角色類

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

  具體享元角色類ConcreteFlyweight有一個內蘊狀態,在本例中一個Character類型的intrinsicState屬性代表,它的值應當在享元對象被創建時賦予。所有的內蘊狀態在對象創建之後,就不會再改變了。

  如果一個享元對象有外蘊狀態的話,所有的外部狀態都必須存儲在客戶端,在使用享元對象時,再由客戶端傳入享元對象。這裏只有一個外蘊狀態,operation()方法的參數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);
    }

}
複製代碼

  享元工廠角色類,必須指出的是,客戶端不可以直接將具體享元類實例化,而必須通過一個工廠對象,利用一個factory()方法得到享元對象。一般而言,享元工廠對象在整個系統中只有一個,因此也可以使用單例模式。

  當客戶端需要單純享元對象的時候,需要調用享元工廠的factory()方法,並傳入所需的單純享元對象的內蘊狀態,由工廠方法產生所需要的享元對象。

複製代碼
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");
    }

}
複製代碼

  雖然客戶端申請了三個享元對象,但是實際創建的享元對象只有兩個,這就是共享的含義。運行結果如下:

 

複合享元模式

  在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共享的。還有一種較爲複雜的情況,將一些單純享元使用合成模式加以複合,形成複合享元對象。這樣的複合享元對象本身不能共享,但是它們可以分解成單純享元對象,而後者則可以共享。

  

  複合享元角色所涉及到的角色如下:

  ●  抽象享元(Flyweight)角色 :給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。

  ●  具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。

  ●   複合享元(ConcreteCompositeFlyweight)角色 :複合享元角色所代表的對象是不可以共享的,但是一個複合享元對象可以分解成爲多個本身是單純享元對象的組合。複合享元角色又稱作不可共享的享元對象。

  ●   享元工廠(FlyweightFactory)角色 :本角 色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有 一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個 合適的享元對象。

源代碼

  抽象享元角色類

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);
    }

}
複製代碼

  複合享元對象是由單純享元對象通過複合而成的,因此它提供了add()這樣的聚集管理方法。由於一個複合享元對象具有不同的聚集元素,這些聚集元素在複合享元對象被創建之後加入,這本身就意味着複合享元對象的狀態是會改變的,因此複合享元對象是不能共享的。

  複合享元角色實現了抽象享元角色所規定的接口,也就是operation()方法,這個方法有一個參數,代表複合享元對象的外蘊狀態。一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的;而一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的,不然就沒有使用價值了。

複製代碼
public class ConcreteCompositeFlyweight implements Flyweight {
    
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
    /**
     * 增加一個新的單純享元對象到聚集中
     */
    public void add(Character key , Flyweight fly){
        files.put(key,fly);
    }
    /**
     * 外蘊狀態作爲參數傳入到方法中
     */
    @Override
    public void operation(String state) {
        Flyweight fly = null;
        for(Object o : files.keySet()){
            fly = files.get(o);
            fly.operation(state);
        }
        
    }

}
複製代碼

  享元工廠角色提供兩種不同的方法,一種用於提供單純享元對象,另一種用於提供複合享元對象。

複製代碼
public class FlyweightFactory {
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();
    /**
     * 複合享元工廠方法
     */
    public Flyweight factory(List<Character> compositeState){
        ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
        
        for(Character state : compositeState){
            compositeFly.add(state,this.factory(state));
        }
        
        return compositeFly;
    }
    /**
     * 單純享元工廠方法
     */
    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) {
        List<Character> compositeState = new ArrayList<Character>();
        compositeState.add('a');
        compositeState.add('b');
        compositeState.add('c');
        compositeState.add('a');
        compositeState.add('b');
        
        FlyweightFactory flyFactory = new FlyweightFactory();
        Flyweight compositeFly1 = flyFactory.factory(compositeState);
        Flyweight compositeFly2 = flyFactory.factory(compositeState);
        compositeFly1.operation("Composite Call");
        
        System.out.println("---------------------------------");        
        System.out.println("複合享元模式是否可以共享對象:" + (compositeFly1 == compositeFly2));
        
        Character state = 'a';
        Flyweight fly1 = flyFactory.factory(state);
        Flyweight fly2 = flyFactory.factory(state);
        System.out.println("單純享元模式是否可以共享對象:" + (fly1 == fly2));
    }
}
複製代碼

 

運行結果如下:

  從運行結果可以看出,一個複合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的。即外運狀態都等於Composite Call。

  從運行結果可以看出,一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的。即內蘊狀態分別爲b、c、a。

  從運行結果可以看出,複合享元對象是不能共享的。即使用相同的對象compositeState通過工廠分別兩次創建出的對象不是同一個對象。

  從運行結果可以看出,單純享元對象是可以共享的。即使用相同的對象state通過工廠分別兩次創建出的對象是同一個對象。

享元模式的優缺點

  享元模式的優點在於它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:

  ●  享元模式使得系統更加複雜。爲了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯複雜化。

  ●  享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章