享元模式

簡介

Use sharing to support large numbers of fine-grained objects efficiently.
使用共享對象可有效地支持大量的細(xì)粒度的對象。

享元模式(Flyweight)又稱為 輕量級模式,它是一種對象結(jié)構(gòu)型模式。

面向?qū)ο蠹夹g(shù)可以很好地解決一些靈活性或可擴(kuò)展性問題豆挽,但在很多情況下需要在系統(tǒng)中增加類和對象的個數(shù)吴侦。當(dāng)對象數(shù)量太多時魁淳,將導(dǎo)致運(yùn)行代價過高臊恋,帶來性能下降等問題。享元模式 正是為解決這一類問題而誕生的篡悟。

享元模式 是對象池的一種實(shí)現(xiàn)。類似于線程池匾寝,線程池可以避免不停的創(chuàng)建和銷毀多個對象搬葬,消耗性能。享元模式 也是為了減少內(nèi)存的使用艳悔,避免出現(xiàn)大量重復(fù)的創(chuàng)建銷毀對象的場景急凰。

享元模式 的宗旨是共享細(xì)粒度對象,將多個對同一對象的訪問集中起來猜年,不必為每個訪問者創(chuàng)建一個單獨(dú)的對象抡锈,以此來降低內(nèi)存的消耗疾忍。

享元模式 把一個對象的狀態(tài)分成內(nèi)部狀態(tài)和外部狀態(tài),內(nèi)部狀態(tài)即是不變的企孩,外部狀態(tài)是變化的锭碳;然后通過共享不變的部分,達(dá)到減少對象數(shù)量并節(jié)約內(nèi)存的目的勿璃。

享元模式 本質(zhì):緩存共享對象擒抛,降低內(nèi)存消耗

主要解決

當(dāng)系統(tǒng)中多處需要同一組信息時,可以把這些信息封裝到一個對象中补疑,然后對該對象進(jìn)行緩存歧沪,這樣,一個對象就可以提供給多處需要使用的地方莲组,避免大量同一對象的多次創(chuàng)建诊胞,消耗大量內(nèi)存空間。

享元模式 其實(shí)就是 工廠模式 的一個改進(jìn)機(jī)制锹杈,享元模式 同樣要求創(chuàng)建一個或一組對象撵孤,并且就是通過工廠方法生成對象的,只不過 享元模式 中為工廠方法增加了緩存這一功能竭望。

優(yōu)缺點(diǎn)

優(yōu)點(diǎn)

  • 享元模式 可以極大減少內(nèi)存中對象的數(shù)量邪码,使得相同對象或相似對象在內(nèi)存中只保存一份,降低內(nèi)存占用咬清,增強(qiáng)程序的性能闭专;
  • 享元模式 的外部狀態(tài)相對獨(dú)立,而且不會影響其內(nèi)部狀態(tài)旧烧,從而使得享元對象可以在不同的環(huán)境中被共享影钉;

缺點(diǎn)

  • 享元模式 使得系統(tǒng)更加復(fù)雜,需要分離出內(nèi)部狀態(tài)和外部狀態(tài)掘剪,這使得程序的邏輯復(fù)雜化平委;
  • 為了使對象可以共享,享元模式 需要將享元對象的狀態(tài)外部化夺谁,而且外部狀態(tài)必須具備固化特性肆汹,不應(yīng)該隨內(nèi)部狀態(tài)改變而改變,否則會導(dǎo)致系統(tǒng)的邏輯混亂予权;

使用場景

  • 系統(tǒng)中存在大量的相似對象昂勉;
  • 細(xì)粒度的對象都具備較接近的外部狀態(tài),而且內(nèi)部狀態(tài)與環(huán)境無關(guān)扫腺,也就是說對象沒有特定身份岗照;
  • 需要緩沖池的場景;

模式講解

首先來看下 享元模式 的通用 UML 類圖:

享元模式

從 UML 類圖中,我們可以看到攒至,享元模式 主要包含三種角色:

  • 抽象享元角色(Flyweight):享元對象抽象基類或者接口厚者,同時定義出對象的外部狀態(tài)和內(nèi)部狀態(tài)的接口或?qū)崿F(xiàn);
  • 具體享元角色(ConcreteFlyweight):實(shí)現(xiàn)抽象角色定義的業(yè)務(wù)迫吐。該角色的內(nèi)部狀態(tài)處理應(yīng)該與環(huán)境無關(guān)库菲,不能出現(xiàn)會有一個操作改變內(nèi)部狀態(tài),同時修改了外部狀態(tài)志膀;
  • 享元工廠(FlyweightFactory):負(fù)責(zé)管理享元對象池和創(chuàng)建享元對象熙宇;

以下是 享元模式 的通用代碼:

class Client {
    public static void main(String[] args) {
        IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
        IFlyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
        flyweight1.operation("a");
        flyweight2.operation("b");
    }

    // 抽象享元角色
    interface IFlyweight {
        void operation(String extrinsicState);
    }

    // 具體享元角色
    static class ConcreteFlyweight implements IFlyweight {
        private String intrinsicState;

        public ConcreteFlyweight(String intrinsicState) {
            this.intrinsicState = intrinsicState;
        }

        @Override
        public void operation(String extrinsicState) {
            System.out.println("Object address: " + System.identityHashCode(this));
            System.out.println("IntrinsicState: " + this.intrinsicState);
            System.out.println("ExtrinsicState: " + extrinsicState);
        }
    }

    // 享元工廠
    static class FlyweightFactory {
        private static Map<String, IFlyweight> pool = new HashMap<>();

        // 因?yàn)閮?nèi)部狀態(tài)具備不變性,因此作為緩存的鍵
        public static IFlyweight getFlyweight(String intrinsicState) {
            if (!pool.containsKey(intrinsicState)) {
                IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
                pool.put(intrinsicState, flyweight);
            }
            return pool.get(intrinsicState);
        }
    }
}

舉個例子

例子:我們知道溉浙,過年回家的時候很麻煩烫止,因?yàn)槲覀冃枰獡尩揭粡埢丶业幕疖嚻薄屍钡臅r候戳稽,我們肯定是要查詢下有沒有我們需要的票信息馆蠕,這里我們假設(shè)一張火車的信息包含:出發(fā)站,目的站惊奇,價格互躬,座位類別。現(xiàn)在要求編寫一個查詢火車票查詢偽代碼颂郎,可以通過出發(fā)站吼渡,目的站查到相關(guān)票的信息。

直接思路:例子要求通過出發(fā)站祖秒,目的站查詢火車票的相關(guān)信息,那么我們只需構(gòu)建出火車票類對象舟奠,然后提供一個查詢出發(fā)站竭缝,目的站的接口給到客戶進(jìn)行查詢即可;
具體代碼如下:

class Client {
    public static void main(String[] args) {
        ITicket ticket = TicketFactory.queryTicket("深圳北", "潮汕");
        ticket.showInfo("硬座");
    }

    interface ITicket {
        void showInfo(String bunk);
    }

    static class TrainTicket implements ITicket {
        private String from;
        private String to;

        public TrainTicket(String from, String to) {
            this.from = from;
            this.to = to;
        }

        @Override
        public void showInfo(String bunk) {
            int price = new Random().nextInt(500);
            System.out.println(String.format("%s->%s:%s價格:%s 元", this.from, this.to, bunk, price));
        }
    }

    static class TicketFactory {
        public static ITicket queryTicket(String from, String to) {
            return new TrainTicket(from, to);
        }
    }
}

分析:上面的代碼中沼瘫,客戶端進(jìn)行查詢時抬纸,系統(tǒng)通過TicketFactory直接創(chuàng)建一個火車票對象,但是這樣做的話耿戚,當(dāng)某個瞬間如果有大量的用戶請求同一張票的信息時湿故,系統(tǒng)就會創(chuàng)建出大量該火車票對象,系統(tǒng)內(nèi)存壓力驟增膜蛔。而其實(shí)更好的做法應(yīng)該是緩存該票對象坛猪,然后復(fù)用提供給其他查詢請求,這樣一個對象就足以支撐數(shù)以千計(jì)的查詢請求皂股,對內(nèi)存完全無壓力墅茉,使用 享元模式 可以很好地解決這個問題;
具體代碼如下:只需對上面代碼的TicketFactory進(jìn)行更改,增加緩存機(jī)制:

class Client {
    public static void main(String[] args) {
        ITicket ticket = TicketFactory.queryTicket("深圳北", "潮汕");
        ticket.showInfo("硬座");
        ticket = TicketFactory.queryTicket("深圳北", "潮汕");
        ticket.showInfo("軟座");
        ticket = TicketFactory.queryTicket("深圳北", "潮汕");
        ticket.showInfo("硬臥");
    }
    ...
    ...
    static class TicketFactory {
        private static Map<String, ITicket> sTicketPool = new HashMap<>();

        // 側(cè)重于演示就斤,不考慮性能等問題
        public static synchronized ITicket queryTicket(String from, String to) {
            String key = from + "->" + to;
            if (TicketFactory.sTicketPool.containsKey(key)) {
                System.out.println("使用緩存 ==> " + key);
                return TicketFactory.sTicketPool.get(key);
            }
            System.out.println("第一次查詢悍募,創(chuàng)建對象 ==> " + key);
            ITicket ticket = new TrainTicket(from, to);
            TicketFactory.sTicketPool.put(key, ticket);
            return ticket;
        }
    }
}

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

第一次查詢,創(chuàng)建對象 ==> 深圳北->潮汕
深圳北->潮汕:硬座價格:429 元
使用緩存 ==> 深圳北->潮汕
深圳北->潮汕:軟座價格:321 元
使用緩存 ==> 深圳北->潮汕
深圳北->潮汕:硬臥價格:481 元

可以看到洋机,除了第一次查詢創(chuàng)建對象后坠宴,后續(xù)查詢相同車次票信息都是使用緩存對象,無需創(chuàng)建新對象了绷旗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喜鼓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子刁标,更是在濱河造成了極大的恐慌颠通,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膀懈,死亡現(xiàn)場離奇詭異顿锰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)启搂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門硼控,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胳赌,你說我怎么就攤上這事牢撼。” “怎么了疑苫?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵熏版,是天一觀的道長。 經(jīng)常有香客問我捍掺,道長撼短,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任挺勿,我火速辦了婚禮曲横,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘不瓶。我一直安慰自己禾嫉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布蚊丐。 她就那樣靜靜地躺著熙参,像睡著了一般。 火紅的嫁衣襯著肌膚如雪麦备。 梳的紋絲不亂的頭發(fā)上尊惰,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天讲竿,我揣著相機(jī)與錄音,去河邊找鬼弄屡。 笑死题禀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的膀捷。 我是一名探鬼主播迈嘹,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼全庸!你這毒婦竟也來了秀仲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤壶笼,失蹤者是張志新(化名)和其女友劉穎神僵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體覆劈,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡保礼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了责语。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片炮障。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖坤候,靈堂內(nèi)的尸體忽然破棺而出胁赢,到底是詐尸還是另有隱情,我是刑警寧澤白筹,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布智末,位于F島的核電站,受9級特大地震影響徒河,放射性物質(zhì)發(fā)生泄漏系馆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一虚青、第九天 我趴在偏房一處隱蔽的房頂上張望它呀。 院中可真熱鬧螺男,春花似錦棒厘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淆院,卻和暖如春何乎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工支救, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抢野,地道東北人。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓各墨,卻偏偏與公主長得像指孤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贬堵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容