Java設(shè)計模式—享元模式

享元模式采用共享機制來避免大量擁有相同內(nèi)容對象的開銷。這種開銷最常見搔预、最直觀的就是內(nèi)存的損耗霹期。享元對象能做到共享的關(guān)鍵是區(qū)分內(nèi)蘊狀態(tài)(Internal State)和外蘊狀態(tài)(External State)。

  • 內(nèi)蘊狀態(tài)是存儲在享元對象內(nèi)部的拯田,并且是不會隨環(huán)境的改變而有所不同历造。因此,一個享元可以具有內(nèi)蘊狀態(tài)并可以共享船庇。
  • 外蘊狀態(tài)是隨環(huán)境的改變而改變的吭产、不可以共享的。享元對象的外蘊狀態(tài)必須由客戶端保存溢十,并在享元對象被創(chuàng)建之后垮刹,在需要使用的時候再傳入到享元對象內(nèi)部达吞。外蘊狀態(tài)不可以影響享元對象的內(nèi)蘊狀態(tài)张弛,它們是相互獨立的。

組成結(jié)構(gòu)

享元模式一般有三個角色:

  • 抽象享元(Flyweight)角色 :給出一個抽象接口酪劫,以規(guī)定出所有具體享元角色需要實現(xiàn)的方法吞鸭。
  • 具體享元(ConcreteFlyweight)角色:實現(xiàn)抽象享元角色所規(guī)定出的接口。如果有內(nèi)蘊狀態(tài)的話覆糟,必須負(fù)責(zé)為內(nèi)蘊狀態(tài)提供存儲空間刻剥。
  • 享元工廠(FlyweightFactory)角色 :本角色負(fù)責(zé)創(chuàng)建和管理享元角色。本角色必須保證享元對象可以被系統(tǒng)適當(dāng)?shù)毓蚕硖沧帧.?dāng)一個客戶端對象調(diào)用一個享元對象的時候造虏,享元工廠角色會檢查系統(tǒng)中是否已經(jīng)有一個符合要求的享元對象御吞。如果已經(jīng)有了,享元工廠角色就應(yīng)當(dāng)提供這個已有的享元對象漓藕;如果系統(tǒng)中沒有一個適當(dāng)?shù)南碓獙ο蟮脑捥罩椋碓S角色就應(yīng)當(dāng)創(chuàng)建一個合適的享元對象。

簡單應(yīng)用

以象棋游戲為例享钞,假設(shè)我們把每顆棋子看成是一個對象揍诽,那么每開啟一個棋局需要創(chuàng)建32個棋子對象,那如果同時存在一百萬個棋局的話就很可怕了@跏J畲唷!我們試著用享元模式解決這個問題狐肢。

首先劃分外蘊狀態(tài)和內(nèi)蘊狀態(tài)

外蘊狀態(tài):不同的棋子角色是不確定的添吗,創(chuàng)建棋子的時候才會確認(rèn)是什么角色。
內(nèi)蘊狀態(tài):棋子的形狀和大小基本是不會變化的份名,不會隨著棋子角色變化而變化根资。

1、抽象享元(Flyweight)角色

定義一個創(chuàng)建棋子的接口

public interface IChess {
    /**
     * 棋子信息
     */
    void info();
}
2同窘、具體享元(ConcreteFlyweight)角色

實現(xiàn)棋子的創(chuàng)建并打印棋子信息

public class Chess implements IChess {

    public static final String TAG = "Chess";

    //可變
    private String role; //棋子角色

    //不可變
    private String shape = "CIRCLE";  //棋子形狀
    private int radius = 100; //棋子半徑大小

    public Chess(String role) {
        this.role = role;
    }

    @Override
    public void info() {
        Log.d(TAG, String.format("角色%s玄帕,形狀%s,大小%d", role, shape, radius));
    }
}
3想邦、享元工廠(FlyweightFactory)角色

負(fù)責(zé)創(chuàng)建棋子裤纹,使用HashMap保存已創(chuàng)建的棋子達(dá)到復(fù)用目的

public class ChessFactory {

    private static HashMap<String, Chess> chessHashMap = new HashMap<>(); //負(fù)責(zé)存儲共享對象

    //如果共享Map內(nèi)已經(jīng)存在role角色的棋子,直接復(fù)用丧没;否則創(chuàng)建新棋子
    public static Chess getChess(String role) {
        Chess chess = chessHashMap.get(role);
        if (chess == null) {
            Log.d(TAG, "=================創(chuàng)建一個新的棋子=================");
            chess = new Chess(role);
            chessHashMap.put(role, chess);
        }
        return chess;
    }
}

隨機創(chuàng)建30個棋子(六種角色)鹰椒,查看程序運行結(jié)果:

public void button(View view) {
    String[] roles = {"將", "帥", "車", "馬", "炮", "兵"};
    //創(chuàng)建30個棋子
    for (int i = 0; i < 30; i++) {
        ChessFactory.getChess(roles[(int) (Math.random() * 1000) % 6]).createChess();
    }
}
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色車,形狀CIRCLE呕童,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色炮漆际,形狀CIRCLE,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色馬夺饲,形狀CIRCLE奸汇,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色兵,形狀CIRCLE往声,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色炮擂找,形狀CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色馬浩销,形狀CIRCLE贯涎,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE慢洋,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色兵塘雳,形狀CIRCLE陆盘,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE败明,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================創(chuàng)建一個新的棋子=================
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥礁遣,形狀CIRCLE,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥肩刃,形狀CIRCLE祟霍,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色車,形狀CIRCLE盈包,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帥沸呐,形狀CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色車呢燥,形狀CIRCLE崭添,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE叛氨,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色馬呼渣,形狀CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色馬寞埠,形狀CIRCLE屁置,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帥,形狀CIRCLE仁连,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帥蓝角,形狀CIRCLE,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色兵饭冬,形狀CIRCLE使鹅,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色馬,形狀CIRCLE昌抠,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色馬患朱,形狀CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色炮炊苫,形狀CIRCLE裁厅,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色將,形狀CIRCLE劝评,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色將姐直,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬蒋畜,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬撞叽,形狀CIRCLE姻成,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色車插龄,形狀CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色馬科展,形狀CIRCLE均牢,大小100

從結(jié)果可以看出,每個角色的棋子都只有在第一次使用的時候需要創(chuàng)建才睹,也就是說棋子對象的個數(shù)只與角色數(shù)量有關(guān)徘跪。這樣就成功實現(xiàn)了對象的共享復(fù)用,減少了因重復(fù)創(chuàng)建相同內(nèi)容的對象帶來的內(nèi)存開銷琅攘。

優(yōu)缺點

優(yōu)點
  • 極大的減少系統(tǒng)中對象的個數(shù)垮庐,降低內(nèi)存的消耗;
  • 享元模式 的外部狀態(tài)相對獨立坞琴,而且不會影響其內(nèi)部狀態(tài)哨查,從而使得享元對象可以在不同的環(huán)境中被共享。
缺點

為了使對象可以共享剧辐,需要劃分內(nèi)蘊狀態(tài)和外蘊狀態(tài)寒亥,使得程序的設(shè)計變得復(fù)雜。

享元模式與對象池的區(qū)別

相同點:

享元模式和對象池的最終目標(biāo)是相同的荧关,都是為了減少對象的數(shù)量溉奕,減少內(nèi)存的使用。它們都是通過維護和共享一組對象實現(xiàn)對象的復(fù)用忍啤。

不同點:

享元模式是結(jié)構(gòu)型模式腐宋。它把可以變化的狀態(tài)剝離出來并對外提供接口,并且共享不變的東西檀轨。享元對外提供的接口常常會包含一個String類型的參數(shù)胸竞,通常參數(shù)與對象是一對一的關(guān)系。
而對象池是構(gòu)造型模式参萄,側(cè)重于提供整個對象實例卫枝。對調(diào)用者而言對象池提供的對象都沒有區(qū)別,這個可以用讹挎,那個也可以用校赤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市筒溃,隨后出現(xiàn)的幾起案子马篮,更是在濱河造成了極大的恐慌,老刑警劉巖怜奖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浑测,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機迁央,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門掷匠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岖圈,你說我怎么就攤上這事讹语。” “怎么了蜂科?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵顽决,是天一觀的道長。 經(jīng)常有香客問我导匣,道長才菠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任逐抑,我火速辦了婚禮鸠儿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘厕氨。我一直安慰自己进每,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布命斧。 她就那樣靜靜地躺著田晚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪国葬。 梳的紋絲不亂的頭發(fā)上贤徒,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音汇四,去河邊找鬼接奈。 笑死,一個胖子當(dāng)著我的面吹牛通孽,可吹牛的內(nèi)容都是我干的序宦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼背苦,長吁一口氣:“原來是場噩夢啊……” “哼互捌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起行剂,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤秕噪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后厚宰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腌巾,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了壤躲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片城菊。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡备燃,死狀恐怖碉克,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情并齐,我是刑警寧澤漏麦,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站况褪,受9級特大地震影響撕贞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜测垛,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一捏膨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧食侮,春花似錦号涯、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至眉尸,卻和暖如春域蜗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背噪猾。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工霉祸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袱蜡。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓丝蹭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親戒劫。 傳聞我的和親對象是個殘疾皇子半夷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353