java設(shè)計(jì)模式-享元模式(Flyweight)

定義

Flyweight在拳擊比賽中指最輕量級(jí),即“蠅量級(jí)”或“雨量級(jí)”。這里選擇使用“享元模式”的意譯,是因?yàn)檫@樣更能反映出模式的用意。享元模式是對(duì)象的結(jié)構(gòu)模式。享元模式以共享的方式高效的支持大量的細(xì)粒度對(duì)象。

Java中的String類(lèi)型

在Java語(yǔ)言中,String類(lèi)型就是使用了享元模式。String對(duì)象是final類(lèi)型,對(duì)象一旦創(chuàng)建就不可改變。在Java中字符串常量都是存儲(chǔ)在常量池中的,Java會(huì)確保一個(gè)字符串常量在常量池中只有一個(gè)拷貝。String a = "abc",其中"abc"就是一個(gè)字符串常量。

public class Test {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        System.out.println(a==b);
    }
}

上面的例子中,結(jié)果為true,這說(shuō)明a和b兩個(gè)引用都指向了常量池中的同一個(gè)字符串常量abc。這樣的設(shè)計(jì)避免了在創(chuàng)建N多個(gè)相同對(duì)象時(shí)所產(chǎn)生的不必要的大量的資源消耗。

享元模式的結(jié)構(gòu)

享元模式采用一個(gè)共享來(lái)避免大量擁有相同內(nèi)容對(duì)象的開(kāi)銷(xiāo)。這種開(kāi)銷(xiāo)最常見(jiàn)、最直觀的就是內(nèi)存的損耗。享元對(duì)象能做到共享的關(guān)鍵在于能區(qū)分內(nèi)蘊(yùn)狀態(tài)(Internal State)外蘊(yùn)狀態(tài)(External State)。

一個(gè)內(nèi)蘊(yùn)狀態(tài)是存儲(chǔ)在享元對(duì)象內(nèi)部的,并且是不會(huì)隨環(huán)境的改變而有所不同。因此,一個(gè)享元可以有內(nèi)蘊(yùn)狀態(tài)并可以共享。

一個(gè)外蘊(yùn)狀態(tài)是隨環(huán)境的改變而改變的、不可以共享的。享元對(duì)象的外蘊(yùn)狀態(tài)必須由客戶端保存,并在享元對(duì)象被創(chuàng)建之后,在需要使用的時(shí)候再傳入到享元對(duì)象內(nèi)部。外蘊(yùn)狀態(tài)不可以影響享元對(duì)象的內(nèi)蘊(yùn)狀態(tài),它們是相互獨(dú)立的。

享元模式可以分為單純享元模式復(fù)合享元模式兩種形勢(shì)。

單純享元模式

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

單純享元模式結(jié)構(gòu)
單純享元模式結(jié)構(gòu)

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

  • 抽象享元(Flyweight)角色:給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色需要實(shí)現(xiàn)的方法。
  • 具體享元(ConcreteFlyweight)角色:實(shí)現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
  • 享元工廠(FlyweightFactory)角色:本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)墓蚕?。?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)又一個(gè)符合要求的享元對(duì)象。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒(méi)有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)合適的享元對(duì)象。

示例代碼

抽象享元角色類(lèi)

public interface Flyweight {
    //一個(gè)示意性方法,參數(shù)state時(shí)外運(yùn)狀態(tài),由外部傳入
    public void operation(String state);
}

具體享元角色類(lèi)ConcreteFlyweight有一個(gè)內(nèi)蘊(yùn)狀態(tài),在本例中,使用Character類(lèi)型的intrinsicState屬性代表,它的值應(yīng)當(dāng)是在享元模式被創(chuàng)建時(shí)賦予。所有的內(nèi)蘊(yùn)狀態(tài),在對(duì)象創(chuàng)建完成之后,就不會(huì)再改變了。

如果一個(gè)享元模式有內(nèi)蘊(yùn)狀態(tài)的話,所有的外部狀態(tài)必須存儲(chǔ)在客戶端,在使用享元對(duì)象時(shí),再由客戶端傳入享元對(duì)象。這里只有一個(gè)外蘊(yùn)狀態(tài),operation(String state)state參數(shù)就是由外部傳入的外蘊(yùn)狀態(tài)。

public class ConcreteFlyweight implements Flyweight {
    private Character intrinsicState = null;
    
    public ConcreteFlyweight(Character intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    
    /**
     * 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為
     * 但是并不改變方法的內(nèi)蘊(yùn)狀態(tài)
     */
    @Override
    public void operation(String state) {
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }
}

享元工廠角色類(lèi)。必須指出的是,客戶端不可以直接將具體享元類(lèi)實(shí)例化,而必須通過(guò)一個(gè)工廠對(duì)象,利用一個(gè)factory()方法得到享元對(duì)象。一般而言,享元工廠對(duì)象在整個(gè)系統(tǒng)中只有一個(gè),因此也可以使用單例模式。其實(shí)在這個(gè)例子中,單例模式跟享元模式可以替代使用。

當(dāng)客戶端需要單純享元對(duì)象的時(shí)候,需要調(diào)用享元工廠的factory()方法,并傳入所需的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài),由工廠方法產(chǎn)生所需要的享元對(duì)象。

public class FlyweightFactory {
    private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
    
    public Flyweight factory(Character state) {
        //先從已有的緩存列表中查詢對(duì)象是否已存在
        Flyweight flyweight = files.get(state);
        if (flyweight == null) {
            //如果對(duì)象不存在,則重新創(chuàng)建一個(gè)新的Flyweight對(duì)象
            flyweight = new ConcreteFlyweight(state);
            //將新生成的對(duì)象放入緩存列表中
            files.put(state, flyweight);
        }
        //返回對(duì)象
        return flyweight;
    }
}

客戶端類(lèi)

public class Client {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();
        
        Flyweight flyweightA = factory.factory(new Character('A'));
        flyweightA.operation("First Call , A State");
        System.out.println(flyweightA);
        
        Flyweight flyweightB = factory.factory(new Character('B'));
        flyweightB.operation("Second Call , B State");
        System.out.println(flyweightB);
        
        Flyweight flyweightC = factory.factory(new Character('A'));
        flyweightC.operation("Third Call , A State");
        System.out.println(flyweightC);
    }
}

雖然客戶端申請(qǐng)了三個(gè)享元對(duì)象,但是實(shí)際創(chuàng)建的享元對(duì)象只有兩個(gè),這就是共享的含義,運(yùn)行的結(jié)果如下:

Intrinsic State = A
Extrinsic State = First Call , A State
com.sschen.flyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Second Call , B State
com.sschen.flyweight.ConcreteFlyweight@15db9742
Intrinsic State = A
Extrinsic State = Third Call , A State
com.sschen.flyweight.ConcreteFlyweight@2a139a55

復(fù)合享元模式

在單純享元模式中,所有的享元對(duì)象都是單純享元對(duì)象,也就是說(shuō)是可以共享的。還有一種較為復(fù)雜的情況,將一些單純享元對(duì)象使用合成模式加以復(fù)合,形成復(fù)合享元對(duì)象。這樣的復(fù)合對(duì)象本身不能共享,但是它們可以分解為單純享元對(duì)象,而后者則可以共享。

復(fù)合享元模式結(jié)構(gòu)

復(fù)合享元角色所涉及到的角色如下:

  • 抽象享元角色(Flyweight):給出一個(gè)抽象接口,以規(guī)定出所有具體享元角色所需要實(shí)現(xiàn)的方法。
  • 具體享元角色(ConcreteFlyweight):實(shí)現(xiàn)抽象享元角色所規(guī)定的借口。如果有內(nèi)蘊(yùn)狀態(tài)的話,必須負(fù)責(zé)為內(nèi)蘊(yùn)狀態(tài)提供存儲(chǔ)空間。
  • 復(fù)合享元角色(ConcreteCompositeFlyweight):復(fù)合享元角色所代表的對(duì)象是不可以共享的,但是一個(gè)復(fù)合享元對(duì)象可以分解成為多個(gè)本身是單純享元對(duì)象的組合。復(fù)合享元對(duì)象又稱(chēng)作不可共享的享元對(duì)象。
  • 享元工廠角色(FlyweightFactory):本角色需要負(fù)責(zé)創(chuàng)建和管理角色。本角色必須保證享元對(duì)象可以被系統(tǒng)適當(dāng)?shù)墓蚕怼.?dāng)一個(gè)客戶端對(duì)象調(diào)用一個(gè)享元對(duì)象的時(shí)候,享元工廠角色會(huì)檢查系統(tǒng)中是否已經(jīng)有一個(gè)符合要求的享元對(duì)象。如果已經(jīng)存在,則享元工廠角色就應(yīng)當(dāng)提供這個(gè)已有的享元對(duì)象;如果系統(tǒng)中沒(méi)有一個(gè)適當(dāng)?shù)南碓獙?duì)象的話,則享元工廠角色就應(yīng)當(dāng)創(chuàng)建一個(gè)合適的享元對(duì)象。

示例代碼

抽象享元角色類(lèi)

public interface Flyweight {
    //一個(gè)示意性方法,參數(shù)state時(shí)外運(yùn)狀態(tài),由外部傳入
    public void operation(String state);
}

具體享元角色類(lèi)

public class ConcreteFlyweight implements Flyweight {
    private Character intrinsicState = null;
    
    public ConcreteFlyweight(Character intrinsicState) {
        this.intrinsicState = intrinsicState;
    }
    
    /**
     * 外蘊(yùn)狀態(tài)作為參數(shù)傳入方法中,改變方法的行為
     * 但是并不改變方法的內(nèi)蘊(yùn)狀態(tài)
     */
    @Override
    public void operation(String state) {
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }
}

復(fù)合享元對(duì)象是由單純享元對(duì)象通過(guò)復(fù)合而成的,因此它提供了add()這樣的聚集管理方法。由于 一個(gè)復(fù)合享元對(duì)象具有不同的聚集元素,這些聚集元素在復(fù)合享元對(duì)象被創(chuàng)建之后加入,這本身就意味著復(fù)合享元對(duì)象的狀態(tài)是會(huì)改變的,因此復(fù)合享元對(duì)象是不能共享的。

復(fù)合享元角色實(shí)現(xiàn)了抽象享元角色所規(guī)定的借口,也就是operation()方法,這個(gè)方法有一個(gè)參數(shù),代表復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)。一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等的;而一個(gè)復(fù)合享元對(duì)象所包含的所有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不相等的,不然就沒(méi)有了使用的價(jià)值。

public class ConcreteCompositeFlyweight implements Flyweight {
    private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
    
    /**
     * 添加一個(gè)新的單純享元對(duì)象到聚集中
     * @param key
     * @param flyweight
     */
    public void add(Character key, Flyweight flyweight) {
        files.put(key, flyweight);
    }
    
    /**
     * 外蘊(yùn)狀態(tài)作為參數(shù)傳遞到方法中
     */
    @Override
    public void operation(String state) {
        Flyweight flyweight = null;
        for (Object o: files.keySet()) {
            flyweight = files.get(o);
            flyweight.operation(state);
            System.out.println(flyweight);
        }
    }
}

享元工廠角色提供兩種不同的方法,一種用于提供單純享元對(duì)象,一種用于提供復(fù)合享元對(duì)象。

public class FlyweightFactory {
    private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();
    
    /**
     * 復(fù)合享元工廠方法
     * @param compositeState
     * @return
     */
    public Flyweight factory(List<Character> compositeState) {
        ConcreteCompositeFlyweight concreteCompositeFlyweight = new ConcreteCompositeFlyweight();
        
        for (Character state : compositeState) {
            concreteCompositeFlyweight.add(state, this.factory(state));
        }
        
        return concreteCompositeFlyweight;
    }
    
    /**
     * 單純享元工廠方法
     * @param state
     * @return
     */
    public Flyweight factory(Character state) {
        //先從已有的緩存列表中查詢對(duì)象是否已存在
        Flyweight flyweight = files.get(state);
        if (flyweight == null) {
            //如果對(duì)象不存在,則重新創(chuàng)建一個(gè)新的Flyweight對(duì)象
            flyweight = new ConcreteFlyweight(state);
            //將新生成的對(duì)象放入緩存列表中
            files.put(state, flyweight);
        }
        //返回對(duì)象
        return flyweight;
    }
}

客戶端角色

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('B');
        compositeState.add('A');
        
        FlyweightFactory flyweightFactory = new FlyweightFactory();
        Flyweight compositeFly1 = flyweightFactory.factory(compositeState);
        Flyweight compositeFly2 = flyweightFactory.factory(compositeState);
        
        compositeFly1.operation("Composite1 Call");
        compositeFly2.operation("Composite2 Call");
        
        System.out.println("---------------------------------------------");
        System.out.println("復(fù)合享元模式是否可以共享對(duì)象:" + (compositeFly1 == compositeFly2));
        System.out.println(compositeFly1);
        System.out.println(compositeFly2);
        
        Character charState = 'A';
        Flyweight flyweight1 = flyweightFactory.factory(charState);
        Flyweight flyweight2 = flyweightFactory.factory(charState);
        System.out.println("單純享元模式是否可以共享對(duì)象:" + (flyweight1 == flyweight2));
    }
}

運(yùn)行結(jié)果如下:

Intrinsic State = A
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@15db9742
Intrinsic State = C
Extrinsic State = Composite1 Call
com.sschen.compositeflyweight.ConcreteFlyweight@6d06d69c
Intrinsic State = A
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@2a139a55
Intrinsic State = B
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@15db9742
Intrinsic State = C
Extrinsic State = Composite2 Call
com.sschen.compositeflyweight.ConcreteFlyweight@6d06d69c
---------------------------------------------
復(fù)合享元模式是否可以共享對(duì)象:false
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@7852e922
com.sschen.compositeflyweight.ConcreteCompositeFlyweight@4e25154f
單純享元模式是否可以共享對(duì)象:true

從運(yùn)行結(jié)果可以看出:

  • 一個(gè)復(fù)合享元對(duì)象的所有單純享元對(duì)象元素的外蘊(yùn)狀態(tài)都是與復(fù)合享元對(duì)象的外蘊(yùn)狀態(tài)相等,也就是上面例子中的Composite1 Call。
  • 一個(gè)復(fù)合享元對(duì)象所含有的單純享元對(duì)象的內(nèi)蘊(yùn)狀態(tài)一般是不想等的,也就是A、B、C
  • 復(fù)合享元對(duì)象是不能共享的。也就是說(shuō),使用相同的對(duì)象compositeState通過(guò)享元工廠角色分別兩次創(chuàng)建出的對(duì)象不是同一個(gè)對(duì)象。
  • 單純享元對(duì)象是可以共享的。也就是相同的對(duì)象state通過(guò)享元工廠角色分別多次創(chuàng)建出的對(duì)象是同一個(gè)對(duì)象。

享元模式的優(yōu)缺點(diǎn)

享元模式的優(yōu)點(diǎn)在于它大幅度的降低內(nèi)存中對(duì)象的數(shù)量。但是,它做到這一點(diǎn)所付出的代價(jià)也是很高的:

  • 享元模式使得系統(tǒng)更加復(fù)雜。為了使對(duì)象可以共享,需要將一些狀態(tài)外部化,這樣使得程序的邏輯復(fù)雜化。
  • 享元模式將享元對(duì)象的狀態(tài)外部化,而讀取外部狀態(tài)使得運(yùn)行時(shí)間稍微變長(zhǎng)。

參考

《JAVA與模式》之享元模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容