設(shè)計(jì)模式學(xué)習(xí)-享元模式

定義

? 享元模式(Flyweight)是對(duì)象池的一種實(shí)現(xiàn)编矾。享元模式用來(lái)盡可能的減少內(nèi)存使用量瓤逼,適用于可能存在大量重復(fù)對(duì)象的場(chǎng)景酥泞,來(lái)緩存可共享的對(duì)象達(dá)到對(duì)象共享给猾,避免創(chuàng)建過(guò)多對(duì)象的效果。
? 享元對(duì)象中,可以共享的狀態(tài)是內(nèi)部狀態(tài)缰冤,內(nèi)部狀態(tài)不會(huì)隨著環(huán)境的變化犬缨,不可以共享的狀態(tài)稱為外部狀態(tài),外部狀態(tài)會(huì)隨著環(huán)境變化而變化棉浸。享元模式中會(huì)創(chuàng)建一個(gè)對(duì)象容器怀薛,經(jīng)典的享元模式中這個(gè)容器是一個(gè)Map,它的key是享元對(duì)象的內(nèi)部狀態(tài)value為享元對(duì)象本身迷郑。使用者通過(guò)內(nèi)部狀態(tài)從享元工廠中獲取享元對(duì)象枝恋,如果有緩存則使用緩存,如果沒(méi)有則創(chuàng)建一個(gè)對(duì)象存入容器中嗡害。

UML圖

享元模式

享元模式焚碌,主要有享元工廠和享元對(duì)象角色

  • Flyweight

    享元對(duì)象的抽象類或者接口

  • ConcreteFlyweight

    具體的享元對(duì)象

  • FlyweightFactory

    享元工廠,負(fù)責(zé)創(chuàng)建享元對(duì)象和管理對(duì)象池

示例

以購(gòu)買火車票為例霸妹,當(dāng)我們?cè)诰W(wǎng)站查詢火車票時(shí)十电,一般會(huì)輸入起始站和終點(diǎn)站查詢,這兩個(gè)值對(duì)于一個(gè)車票對(duì)象來(lái)說(shuō)是不會(huì)變的叹螟,為內(nèi)部狀態(tài)鹃骂,當(dāng)然其價(jià)格會(huì)根據(jù)所選鋪位的不同而不同,為外部狀態(tài)首妖。

/**
 * 車票類的抽象
 */
public interface Ticket {
    //打印車票信息的方法偎漫,傳入車票的鋪位
    void printTicketInfo(String bunk);
}

/**
 * 享元對(duì)象的具體實(shí)現(xiàn)
 * 其內(nèi)部狀態(tài)是起始站from和終點(diǎn)站key
 * 外部狀態(tài)為票價(jià)price,其價(jià)格根據(jù)鋪位的不同而不同
 */
public class TrainTicket implements Ticket {

    private String from;
    private String to;
    private int price;

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

    @Override
    public void printTicketInfo(String bunk) {
        price = new Random().nextInt(500);
        System.out.println("從" + from + "到" + to + "的" + bunk + "票價(jià)為 :" + price + "元");
    }
}

/**
 * 享元工廠
 * 使用ConcurrentHashMap緩存享元對(duì)象
 * 使用from + "-" + to作為key存儲(chǔ)享元對(duì)象
 */
public class TicketFactory {

    private static ConcurrentHashMap<String,Ticket> sTicketMap = new ConcurrentHashMap<>();

    public static Ticket getTicket(String from,String to){
        String key = from + "-" + to;
        if (sTicketMap.containsKey(key)){
            System.out.println("從緩存取" + key);
            return sTicketMap.get(key);
        }else {
            System.out.println("實(shí)例化" + key);
            Ticket ticket = new TrainTicket(from, to);
            sTicketMap.put(key,ticket);
            return ticket;
        }
    }
    
   public static void main(String args[]) {
        /**
         * 客戶端查詢調(diào)用
         */
        Ticket ticket = TicketFactory.getTicket("深圳", "北京");
        ticket.printTicketInfo("硬臥");

        Ticket ticket1 = TicketFactory.getTicket("深圳", "北京");
        ticket1.printTicketInfo("硬座");

        Ticket ticket2 = TicketFactory.getTicket("深圳", "北京");
        ticket2.printTicketInfo("軟臥");
    }
        實(shí)例化深圳-北京
        從深圳到北京的硬臥票價(jià)為 :154元
        從緩存取深圳-北京
        從深圳到北京的硬座票價(jià)為 :427元
        從緩存取深圳-北京
        從深圳到北京的軟臥票價(jià)為 :264元

我們看到示例中只有第一次從工廠中查詢時(shí)新建了對(duì)象有缆,而后面兩次都是從緩存中獲取的象踊。

Android源碼中的享元模式

Android應(yīng)用是事件驅(qū)動(dòng)的,每個(gè)事件都會(huì)轉(zhuǎn)化為一個(gè)系統(tǒng)消息棚壁,即Message杯矩。那么應(yīng)用中很可能就會(huì)產(chǎn)生很多的Message對(duì)象,而Message對(duì)象的獲取就是使用了享元模式袖外。在獲取Message對(duì)象時(shí)史隆,我們一般調(diào)用Obtain方法

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

這里看到有m.next,推測(cè)使用了鏈表結(jié)構(gòu)曼验。繼續(xù)看一下obtain方法涉及到的這幾個(gè)屬性的定義

/ sometimes we store linked lists of these things
/*package*/ Message next;

/** @hide */
public static final Object sPoolSync = new Object();
private static Message sPool;

? sPoolSync為Object類型的同步鎖泌射,而sPool就是一個(gè)Message類型的對(duì)象,每個(gè)Message對(duì)象都有一個(gè)同類型的next字段鬓照,這個(gè)next字段指向下一個(gè)可用的Message熔酷,最后一個(gè)可用的Message的next字段為null。這樣所有可用的Message對(duì)象就通過(guò)next串聯(lián)成了一個(gè)可用的Message池豺裆。

? 這里首先判斷鏈表的表頭sPool為空的話就只new一個(gè)Message對(duì)象返回拒秘。當(dāng)sPool不為空時(shí),將sPool賦值對(duì)象賦值給m,sPool = m.next 將sPool對(duì)象對(duì)象賦值為m的next屬性躺酒,并將m的next節(jié)點(diǎn)置為null押蚤,最后將m對(duì)象返回,這樣就取出了鏈表的表頭羹应。而鏈表中的對(duì)象是什么時(shí)候存進(jìn)去的呢揽碘,我們看一下Message對(duì)象的回收方法

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

這里將當(dāng)前對(duì)象的next節(jié)點(diǎn)指向sPool,并將sPool指向當(dāng)前Message對(duì)象量愧。即將當(dāng)前回收的Message對(duì)象插入的鏈表的表頭钾菊。

? Message通過(guò)在內(nèi)部構(gòu)建一個(gè)鏈表來(lái)維護(hù)一個(gè)被回收的Message對(duì)象的對(duì)象池子帅矗,當(dāng)用戶調(diào)用obtain方法時(shí)偎肃,會(huì)首先從池中取,如果池中沒(méi)有可以復(fù)用的對(duì)象則創(chuàng)建這個(gè)新的Message對(duì)象浑此,這個(gè)Message對(duì)象在使用完后會(huì)回收到這個(gè)對(duì)象池中累颂,以便下一次調(diào)用obtain方法時(shí)可以復(fù)用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凛俱,一起剝皮案震驚了整個(gè)濱河市紊馏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蒲犬,老刑警劉巖朱监,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異原叮,居然都是意外死亡赫编,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門奋隶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)擂送,“玉大人,你說(shuō)我怎么就攤上這事唯欣∴诙郑” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵境氢,是天一觀的道長(zhǎng)蟀拷。 經(jīng)常有香客問(wèn)我,道長(zhǎng)萍聊,這世上最難降的妖魔是什么问芬? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮脐区,結(jié)果婚禮上愈诚,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好炕柔,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布酌泰。 她就那樣靜靜地躺著,像睡著了一般匕累。 火紅的嫁衣襯著肌膚如雪陵刹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天欢嘿,我揣著相機(jī)與錄音衰琐,去河邊找鬼。 笑死炼蹦,一個(gè)胖子當(dāng)著我的面吹牛羡宙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掐隐,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼狗热,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了虑省?” 一聲冷哼從身側(cè)響起匿刮,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎探颈,沒(méi)想到半個(gè)月后熟丸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伪节,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年光羞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片架馋。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狞山,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叉寂,到底是詐尸還是另有隱情萍启,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布屏鳍,位于F島的核電站勘纯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钓瞭。R本人自食惡果不足惜驳遵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望山涡。 院中可真熱鬧堤结,春花似錦唆迁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至瘾带,卻和暖如春鼠哥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背看政。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工朴恳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人允蚣。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓于颖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親厉萝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恍飘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

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