Java設計模式系列十四(享元模式)

mp.weixin.qq.com/s?src=11&am…

前言
java


秋雨綿綿,週末午後,小區涼亭。
面試

李大爺:"你來了。"編程

我:"我來了。"設計模式

李大爺:"我知道你會來的!"bash

我:"我固然會來,你固然知道,不然一天前你又怎會讓我走?"服務器

我目光重落,再次凝視着他,過了好久,才緩緩道:"如今一天已過去。ide

李大爺:"整整一天。"性能

我:"好長的一天。"測試

李大爺:"好短的一天。"ui

我:"雖然我明知今日必死但我不是那種等死的人。"

李大爺:"如今你的事是否已作完,你的心願已了。"

秋雨依舊綿綿,行人寥寥。

李大爺:"出招吧!"

我:"一天前,我敗在你的手下。"

李大爺淡淡道:"也許你本不應敗的,只惋惜你的人太年輕,棋法卻用老了。"

我:"你借我一天時光,讓我去作我本身想作的事,如今一天已過去,我……"

李大爺道:"你是來送死的。"

我:"不錯,我正是來送死的。"

我:"我既然來了,就已抱定必死之心。"

李大爺道:"你不想再多活一天?"

我突然仰面而笑,道:"大丈夫生於世,若不能下棋贏遍小區大爺,廣場舞浪過廣場大媽,快意恩仇,就算再多活一百年,也是生不如死"。

李大爺打斷了個人話,冷冷道:"你本不是個多話的人,我也不是來跟你說話的,你只求速死?"

我:"是。"

李大爺長長吐出口氣,閉上眼瞪,道:"請!請出手,今天仍是你先走第一步。"


上面是我和小區李大爺的故事,我既然還能出如今這兒寫公衆號,大家應該知道故事的結局了,沒錯,我贏了那一盤,下個目標就是小區張大爺了,嘿嘿!不少朋友應該也都玩過五子棋或者圍棋,幾百顆棋子,分爲黑白兩色,棋差一招者,短短數十回合以後就繳械投降;旗鼓至關者你來我往,攻防有序,幾百回合不見勝負,滿盤黑白錯落有致。

好了,言歸正傳,試想一下,若是咱們要用程序來設計圍棋遊戲,黑子181枚,白子180枚,那咱們是否是每下一個子時,都要去new一個棋子對象呢?Java是一門面向對象語言,咱們都知道若是在內存中不停的new新對象時,當對象數量太多時,又回收不及時時,將致使運行代價太高,帶來性能降低等問題。那咱們怎麼去解決這類問題呢?下面,將爲你們講解本篇的重點,Java設計模式之——享元模式。


什麼是享元模式


爲了方便理解,咱們先來看一下享元模式的兩種狀態:

  • 內部狀態(Intrinsic State):是存儲在享元對象內部而且不會隨環境改變而改變的狀態,所以內部狀態能夠共享。

  • 外部狀態(Extrinsic State):是隨環境改變而改變的、不能夠共享的狀態。享元對象的外部狀態必須由客戶端保存,並在享元對象被建立以後,在須要使用的時候再傳入到享元對象內部。一個外部狀態與另外一個外部狀態之間是相互獨立的。


享元模式將一個對象的狀態分爲內部狀態和外部狀態,其中,兩者是相互獨立的,共享相同的內部狀態,經過設置不一樣的外部狀態來改變對象的特徵,讓一個對象擁有不一樣的特徵,但內部狀態始終是共享的,不可改變的。也就是,改變外部狀態不會引發內部狀態改變。

能夠把圍棋想象成享元模式,他們的大小、形狀、顏色是內部狀態,棋子的位置是外部狀態,這樣在設計時,只須要設置黑白棋子兩個對象,黑棋共享黑色的內部狀態,白棋共享白色的內部狀態,棋盤上每一個棋子的位置就是他們的外部狀態,圍棋盤361個交叉點位置,棋子每落一個位置(外部狀態),都不會改變棋子的顏色(內部狀態)。這樣是否是好理解一點。

享元模式通常會結合工廠模式使用,目的是爲了建立一個享元工廠來負責維護享元池(Flyweight Pool),享元池裏存放的是具備相同內部狀態的享元對象。在實際的平常業務的變幻無窮中,可以共享的內部狀態是不多的,因此享元對象通常都設計爲較小的對象,包含的內部狀態也不多,這種對象也成爲細粒度對象。

如今咱們來看一下享元模式的英文定義:

Flyweight Pattern: Use sharing to support large numbers of fine-grained objects efficiently.


翻譯過來就是:運用共享技術有效地支持大量細粒度對象的複用。(Flyweight我不也不懂爲何國內都翻譯成享元,沒找到資料,多是根據這個模式的做用和特性翻譯來的,若是有知道的朋友煩請文末留言告知一聲,謝謝!)

再看一下國內對享元模式的解釋:

享元模式(Flyweight Pattern):運用共享技術有效地支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式,它是一種對象結構型模式。


簡而言之:享元模式的目的就是經過共享不變的部分,達到減小對象數量並節約內存的目的。


享元模式的四個角色


  • Flyweight(抽象享元類):接口或抽象類,聲明公共方法,這些方法能夠向外界提供對象的內部狀態,設置外部狀態。

  • ConcreteFlyweight(具體享元類):實現了抽象享元類,其實例稱爲享元對象。必須是可共享的,須要封裝享元對象的內部狀態;。

  • UnsharedConcreteFlyweight(非共享具體享元類):非共享的享元實現對象,並非全部的享元對象均可以共享,非共享的享元對象一般是享元對象的組合對象。

  • FlyweightFactory(享元工廠類):享元工廠,主要用來建立並管理共享的享元對象,並對外提供訪問共享享元的接口。它針對抽象享元類編程,將各類類型的具體享元對象存儲在一個享元池中,享元池通常設計爲一個存儲「鍵值對」的集合(也能夠是其餘類型的集合),能夠結合工廠模式進行設計;當用戶請求一個具體享元對象時,享元工廠提供一個存儲在享元池中已建立的實例或者建立一個新的實例(若是不存在的話),返回新建立的實例並將其存儲在享元池中。



享元模式的UML圖





代碼實例


我就不用我和李大爺下棋的例子了,以避免在他老大(幼小)的心靈上留下創傷。關於棋子的案例,網上也有不少版本,你們感興趣的能夠本身去看。下面咱們用王者榮耀遊戲來舉例。咱們知道,在一局對戰賽裏,每隔幾分鐘就會出現一波小兵和超級兵,小兵都長的如出一轍,超級兵也是,若是王者團隊在設計小兵出場的時候,每出來一個小兵,就new一個小兵對象,那麼在這個幾百萬甚至更多人同時在線角逐的遊戲裏,服務器壓力根本就頂不住,還能不能好好的、流暢的、愉快的上分了,小學生放學後早就乖乖在家作做業了。

那麼怎樣設計呢?咱們能夠將小兵的體徵、裝配、兵種做爲內部狀態,而後它們在地圖上出擊的方向做爲外部狀態,這樣不管小兵從哪一個方向出擊(外部狀態怎樣改變),都不會改變小兵的體徵和兵種(內部狀態),這樣咱們在開發時,每一個兵種只要有一個享元對象就能夠了。來看代碼:

一、編寫抽象享元類
package com.weiya.mazhichu.designpatterns.flyweight;/** * <p class="detail"> * 功能:抽象享元類 * </p> * * @author Moore * @ClassName Soldier flyweight. * @Version V1.0. * @date 2019.09.03 21:06:52 */public interface SoldierFlyweight {    /**     * <p class="detail">     * 功能:敵軍出擊方法     * </p>     *     * @param direction :     * @author Moore     * @date 2019.09.03 21:06:52     */    public void attack(String direction);}複製代碼


二、編寫具體享元類

package com.weiya.mazhichu.designpatterns.flyweight;/** * <p class="detail"> * 功能:具體享元類 * </p> * * @author Moore * @ClassName Concrete solider flyweight. * @Version V1.0. * @date 2019.09.04 09:45:41 */public class ConcreteSoliderFlyweight implements SoldierFlyweight {    // 內部狀態    private String soliderType;    public ConcreteSoliderFlyweight(String soliderType) {        this.soliderType = soliderType;    }    @Override    public void attack(String direction) {        if("normal".equals(soliderType)){            System.out.println("普通兵加入戰場");        }        if("super".equals(soliderType)){            System.out.println("超級兵加入戰場");        }        System.out.println("出擊方向:"+direction);    }}複製代碼


三、編寫享元工廠

package com.weiya.mazhichu.designpatterns.flyweight;import java.util.HashMap;import java.util.Map;/** * <p class="detail"> * 功能:享元工廠 * </p> * * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */public class SoldierFlyWeightFactory {    //工廠實例    private static SoldierFlyWeightFactory INSTANCE;    // 享元池    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();    private SoldierFlyWeightFactory(){        SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal");        soldierMap.put("normal",normalSoldier);        SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super");        soldierMap.put("super",superSolider);    }    /**     * <p class="detail">     * 功能:獲取工廠實例     * </p>     *     * @return soldier fly weight factory     * @author Moore     * @date 2019.09.03 21:07:02     */    public static SoldierFlyWeightFactory getInstance(){        if(INSTANCE == null){            INSTANCE = new SoldierFlyWeightFactory();            return INSTANCE;        }        return INSTANCE;    }    /**     * <p class="detail">     * 功能:獲取享元對象     * </p>     *     * @param soliderType :     * @return soldier flyweight     * @author Moore     * @date 2019.09.03 21:07:02     */    public SoldierFlyweight getSolider(String soliderType){        return soldierMap.get(soliderType);    }    /**     * <p class="detail">     * 功能:獲取享元池對象數量     * </p>     *     * @return int     * @author Moore     * @date 2019.09.03 21:07:02     */    public int getSoliderSize(){        return soldierMap.size();    }}複製代碼


四、客戶端測試

package com.weiya.mazhichu.designpatterns.flyweight;/** * <p class="detail"> * 功能: * </p> * * @author Moore * @ClassName Honour of kings test. * @Version V1.0. * @date 2019.09.03 21:06:44 */public class HonourOfKingsTest {    public static void main(String[] args) {        System.out.println("敵軍還有五秒到達戰場!");        SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance();        SoldierFlyweight soldier1 = factory.getSolider("normal");        SoldierFlyweight soldier2 = factory.getSolider("normal");        SoldierFlyweight soldier3 = factory.getSolider("normal");        soldier1.attack("上路");        soldier2.attack("中路");        soldier3.attack("下路");        System.out.println(soldier1 == soldier2);        System.out.println(soldier2 == soldier3);        System.out.println("--------------------------");        System.out.println("主宰已被擊敗!");        SoldierFlyweight soldier4 = factory.getSolider("super");        SoldierFlyweight soldier5 = factory.getSolider("super");        SoldierFlyweight soldier6 = factory.getSolider("super");        soldier4.attack("上路");        soldier5.attack("中路");        soldier6.attack("下路");        System.out.println("對方法師殘血,被超級兵打死...");        System.out.println(soldier4 == soldier5);        System.out.println(soldier5 == soldier6);        System.out.println("--------------------------");        System.out.println("該案例一共生成對象:" + factory.getSoliderSize() + "個");    }}複製代碼


查看運行結果:


能夠看出,咱們一共派出了6個小兵,其中3個普通兵,3個超級兵,可是享元池中只有兩個對象(一個普通兵、一個超級兵對象),也就是說,不管派出多少普通兵或者超級兵,不管它們要從哪一路出擊,都不會影響兵的內部狀態,從而讓整個系統的對象大大減小,減小內存消耗,不卡就不影響遊戲體驗,小學生又能夠開心快樂的出來坑人了,可是要以學業爲重哦!



享元模式擴展


在上面的實例中,咱們主要講的是具體的享元對象,也就是全部的享元對象都是必須共享的。可是享元模式的四個角色中還有一個非共享的享元實現對象,什麼意思呢,顧名思義就是享元對象不必定要共享,可是它一般是做爲享元對象的組合對象來使用。從這個層面來講,咱們又把享元對象分爲:
  • 單純享元模式:在單純享元模式中,全部的享元對象都是能夠共享的,即全部抽象享元類的子類均可共享,不存在非共享具體享元類。

  • 複合享元模式:將一些單純享元使用組合模式加以組合,能夠造成複合享元對象,這樣的複合享元對象自己不能共享,可是它們能夠分解成單純享元對象,然後者則能夠共享。(複合的享元對象實現了抽象享元類,它的實例就是非共享的享元實現對象)


複合享元模式中,組成複合享元對象的每一個單純享元對象擁有本身的內部狀態,而每一個單純享元對象的外部狀態都和複合享元對象的外部狀態相同。因此複合享元模式能夠對多個單純享元對象設置相同的外部狀態, 這也是複合享元模式的應用場景。

單純的享元模式我就再也不贅述了,看上面的棋子或者農藥的實例,下面主要說一下組合享元模式,以及它爲什麼非共享,來看代碼:

一、編寫複合享元角色類
package com.weiya.mazhichu.designpatterns.flyweight;import java.util.HashMap;import java.util.Map;/** * <p class="detail"> * 功能: 複合享元角色類(非共享享元實現對象) * </p> * * @author Moore * @ClassName Concrete composite solider flyweight. * @Version V1.0. * @date 2019.09.04 10:56:11 */public class ConcreteCompositeSoliderFlyweight implements SoldierFlyweight {    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();    /**     * <p class="detail">     * 功能: 增長單純享元對象     * </p>     *     * @param soliderType :     * @param flyweight   :     * @author Moore     * @date 2019.09.04 10:56:11     */    public void add(String soliderType,SoldierFlyweight flyweight){        soldierMap.put(soliderType,flyweight);    }    /**     * <p class="detail">     * 功能: flyWeights是單純享元對象的集合,它們具備相同的外部狀態extrinsicState,     *     調用的時候使用循環調用單純享元對象的attack方法     * </p>     *     * @param direction :     * @author Moore     * @date 2019.09.03 21:06:52     */    @Override    public void attack(String direction) {        SoldierFlyweight flyweight = null;        for(String str : soldierMap.keySet()){            flyweight = soldierMap.get(str);            flyweight.attack(direction);        }    }    /**     * 移除單純享元對象.     * @param soliderType     */    private void remove(String soliderType) {        soldierMap.remove(soliderType);    }}複製代碼


二、修改後的享元工廠角色類
package com.weiya.mazhichu.designpatterns.flyweight;import java.util.HashMap;import java.util.List;import java.util.Map;/** * <p class="detail"> * 功能:享元工廠 * </p> * * @author Moore * @ClassName Soldier fly weight factory. * @Version V1.0. * @date 2019.09.03 21:06:58 */public class SoldierFlyWeightFactory {    //工廠實例    private static SoldierFlyWeightFactory INSTANCE;    // 享元池    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();    private SoldierFlyWeightFactory(){        SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal");        soldierMap.put("normal",normalSoldier);        SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super");        soldierMap.put("super",superSolider);    }    /**     * <p class="detail">     * 功能:獲取工廠實例     * </p>     *     * @return soldier fly weight factory     * @author Moore     * @date 2019.09.03 21:07:02     */    public static SoldierFlyWeightFactory getInstance(){        if(INSTANCE == null){            INSTANCE = new SoldierFlyWeightFactory();            return INSTANCE;        }        return INSTANCE;    }    /**     * <p class="detail">     * 功能:獲取享元對象(單純享元工廠方法)     * </p>     *     * @param soliderType :     * @return soldier flyweight     * @author Moore     * @date 2019.09.03 21:07:02     */    public SoldierFlyweight getSolider(String soliderType){        return soldierMap.get(soliderType);    }    /**     * <p class="detail">     * 功能:複合享元工廠方法     * </p>     *     * @param compositeSoliderTypes :     * @return soldier flyweight     * @author Moore     * @date 2019.09.04 11:06:24     */    public SoldierFlyweight getCompositeSolider(List<String> compositeSoliderTypes){        ConcreteCompositeSoliderFlyweight compositeFlyweight = new ConcreteCompositeSoliderFlyweight();        for(String soliderType : compositeSoliderTypes){            compositeFlyweight.add(soliderType,this.getSolider(soliderType));        }        return compositeFlyweight;    }    /**     * <p class="detail">     * 功能:獲取享元池對象數量     * </p>     *     * @return int     * @author Moore     * @date 2019.09.03 21:07:02     */    public int getSoliderSize(){        return soldierMap.size();    }}複製代碼


三、編寫測試類
package com.weiya.mazhichu.designpatterns.flyweight;import java.util.ArrayList;import java.util.List;/** * <p class="detail"> * 功能: 測試單純享元模式和複合享元模式 * </p> * * @author Moore * @ClassName Flyweight test. * @Version V1.0. * @date 2019.09.04 11:08:51 */public class FlyweightTest {    public static void main(String[] args) {        SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance();        String soliderType = "normal";        SoldierFlyweight soldierFlyweight1 = factory.getSolider(soliderType);        SoldierFlyweight soldierFlyweight2 = factory.getSolider(soliderType);        soldierFlyweight1.attack("上路");        soldierFlyweight2.attack("中路");        System.out.println("---------------------------------");        List<String> compositeSoliderType = new ArrayList<String>();        compositeSoliderType.add("normal");        compositeSoliderType.add("super");        compositeSoliderType.add("normal");        compositeSoliderType.add("super");        compositeSoliderType.add("normal");        SoldierFlyweight compositeSoliderFlyeweight1 = factory.getSolider(compositeSoliderType);        SoldierFlyweight compositeSoliderFlyeweight2 = factory.getSolider(compositeSoliderType);        compositeSoliderFlyeweight1.attack("上路");        compositeSoliderFlyeweight2.attack("中路");        System.out.println("---------------------------------");        System.out.println("單純享元模式是否共享對象:" + (soldierFlyweight1 == soldierFlyweight2));        System.out.println("複合享元模式是否共享對象:" + (compositeSoliderFlyeweight1 == compositeSoliderFlyeweight2));    }}複製代碼


查看運行結果:


結合運行結果,再來逐字逐句看一下這一段,你應該就能有所體會了。

複合享元模式中,組成複合享元對象的每一個單純享元對象擁有本身的內部狀態,而每一個單純享元對象的外部狀態都和複合享元對象的外部狀態相同。因此複合享元模式能夠對多個單純享元對象設置相同的外部狀態, 這也是複合享元模式的應用場景。



複合享元模式UML圖



享元模式總結


使用場景
  • 系統有大量類似或者相同對象。因爲這類對象的大量使用,形成內存的大量耗費。

  • 須要緩衝池的場景,(享元池,也就是在須要屢次使用享元對象的時候)。

  • 對象的大部分狀態均可之外部化,能夠將這些外部狀態傳入對象中。


優勢

  • 大大減小對象的建立,下降系統的內存,使效率提升。

  • 享元模式的外部狀態相對獨立,並且不會影響其內部狀態,從而使得享元對象能夠在不一樣的環境中被共享。


缺點

  • 須要分離出外部狀態和內部狀態,提升了系統的複雜度。

  • 讀取享元模式的外部狀態會使得運行時間稍微變長。



好了,享元模式就說到這兒了,篇幅稍長,可能須要慢慢多讀幾遍才能理解。感謝您能看到這兒,但願能給您稍微有點幫助,我就心滿意足了。




爲了感謝您的關注和喜好,碼之初爲正在找工做的小夥伴悄悄的送上一批乾貨,後臺發送」面試「關鍵字,便可領取隨機一份面試資料,祝全部小夥伴步步高昇,前程似錦!

相關文章
相關標籤/搜索