生活中的設(shè)計模式之享原模式

定義

通過共享對象的內(nèi)部狀態(tài),減少相似對象的創(chuàng)建稽荧。

背景

avatar

假設(shè)橘茉,12306的火車票查詢系統(tǒng)是我們開發(fā)的,它的主要功能是向乘客顯示車票(Ticket)信息姨丈,車票中包含車次畅卓、出發(fā)地、目的地蟋恬、票價翁潘、姓名等信息。
下面的偽代碼表示查詢不同車次下每一位乘客的車票信息歼争。
其中外層for循環(huán)表示不同的車次拜马,內(nèi)層for循環(huán)表示該車次的乘客渗勘,ticket.print()表示向乘客顯示車票信息。


public class Client {

    public static void main(String[] args) {

        for (int trainNumber = 0; trainNumber < 10; trainNumber++) {
            for (int i = 0; i < 100; i++) {
                Ticket ticket = new Ticket(trainNumber,to,price,userName,seatOrder);
                ticket.print();
            }
        }
    }
}

上線后一切正常俩莽,但是在臨近春節(jié)時旺坠,一件出乎我們意料的事發(fā)生了:"海"量用戶通過APP、取票機(jī)豹绪、檢票機(jī)都無法查詢車票信息价淌。(拉去祭天吧I暄邸)

問題

avatar

首先瞒津,我們通過監(jiān)控系統(tǒng)發(fā)現(xiàn)內(nèi)存使用量在蹭?蹭?蹭?地往上漲。(心跳加速@ㄊ)
于是巷蚪,我們初步斷定這不是代碼的問題而是內(nèi)存不足的問題。(想甩鍋濒翻?)
接著屁柏,通過日志文件我們又發(fā)現(xiàn)大量Ticket數(shù)據(jù)高度重復(fù):相同車次的Ticket對象的起點、終點有送、票價都是一樣的淌喻,不一樣的僅僅是姓名。(離死不遠(yuǎn)了H刚)
最終裸删,我們定位到了原因:海量相似的Ticket對象耗盡了內(nèi)存空間。(死得其所U笤)
即使上天不給我再來一次的機(jī)會涯塔,我也會選擇享原模式——通過共享減少對象的創(chuàng)建數(shù)量。

方案

avatar

假如清蚀,我們已經(jīng)創(chuàng)建了一個Ticket對象匕荸,現(xiàn)在要創(chuàng)建與之相同車次的另一個Ticket對象;
那么枷邪,享原模式的方式是復(fù)用前一個Ticket對象中相同的數(shù)據(jù)(車次榛搔,出發(fā)地、目的地)东揣,更改不同的數(shù)據(jù)(姓名践惑、座次)來實現(xiàn)另一個Ticket對象的創(chuàng)建。
從程序的角度看運行時創(chuàng)建了兩個不一樣的對象救斑,但從內(nèi)存角度看只創(chuàng)建了一個對象童本,另一個對象是通過共享前一對象的部分?jǐn)?shù)據(jù)來實現(xiàn)的。
在享原模式中脸候,對象中可共享的數(shù)據(jù)被稱為內(nèi)部狀態(tài)穷娱,它通常是不可變的且需要被緩存的部分绑蔫;與之相反,對象中不可共享的數(shù)據(jù)被稱為外部狀態(tài)泵额,它通常是可變的且由客戶端傳入對象配深。

結(jié)構(gòu)

avatar

抽象享原角色(Flyweight): 是一個接口或抽象類,它負(fù)責(zé)定義外部狀態(tài)并將它作為方法的入?yún)ⅰ?/p>

具體享原角色(ConcreteFlyweight):抽象享原角色的實現(xiàn)類嫁盲,它負(fù)責(zé)定義內(nèi)部狀態(tài)并為其提供存儲空間以及保證其不可變篓叶。

享原工廠角色(FlyweightFactory):通常是一個簡單工廠類,它負(fù)責(zé)創(chuàng)建具體享原角色并將其緩存在HashMap中羞秤。

客戶端(Client):它通過享原工廠創(chuàng)建對象缸托,但并不知道創(chuàng)建的對象是否是一個共享對象。

//抽象享原角色
public interface Flyweight  {
    //參數(shù)化外部狀態(tài)
    public void operation(String extrinsic);
}

//具體享原角色
public class ConcreteFlyweight implements Flyweight{
    //存儲內(nèi)部狀態(tài)
    protected String intrinsic;

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

    @Override
    public void operation(String extrinsic) {
        System.out.println("不變的內(nèi)部狀態(tài):"+intrinsic+"瘾蛋,可變的外部狀態(tài):"+extrinsic);
    }
}

//簡單工廠類
public class FlyweightFactory {

    protected static HashMap<String,Flyweight> pool = new HashMap<>();

    public static Flyweight getInstance(String key){
        //復(fù)用已經(jīng)存在的對象
        Flyweight flyweight = pool.get(key);
        if(flyweight==null){
            flyweight = new ConcreteFlyweight("intrinsic");
            //緩存新創(chuàng)建對象
            pool.put(key,flyweight);
        }
        return flyweight;
    }
}

public class Client {

    public static void main(String[] args) {
            List<String> types = new LinkedList<>();
            for (int i = 0; i < 1000; i++) {
                Flyweight flyweightType1 = FlyweightFactory.getInstance(types.get(random()));
                flyweightType1.operation("extrinsic"+i);
            }
    }
}

應(yīng)用

接下來俐镐,我們使用享原模式重構(gòu)一下車票查詢系統(tǒng),讓它減少相似對象的創(chuàng)建提高內(nèi)存的利用率哺哼。

首先佩抹,在ITicket接口中聲明依賴外部狀態(tài)的方法。


public interface ITicket {
    //每個車次的姓名和座位都是不一樣的
    public void print(String userName,String seatOrder);
}

然后取董,創(chuàng)建Ticket并實現(xiàn)ITicket接口棍苹,它只負(fù)責(zé)存儲內(nèi)部狀態(tài)。

public class Ticket implements ITicket{
    //起點
    protected String from;
    //終點
    protected String to;
    //座次
    protected String seatOrder;
    //車次
    protected String trainNumber;

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

    @Override
    public void print(String userName,String seatOrder) {
        System.out.println("from:"+from+",to:"+to+",seatOrder:"+seatOrder+",userName:"+userName);
    }
}

現(xiàn)在茵汰,創(chuàng)建TicketFactory枢里,根據(jù)車次緩存Ticket對象。


public class TicketFactory {

    protected static HashMap<String,ITicket> sharedPart= new HashMap<>();

    public static ITicket getTicket(String trainNumber){

        ITicket ticket = sharedPart.get(trainNumber);
        if(ticket==null){
            ticket = new Ticket("trainNumber","from","to");
            sharedPart.put(trainNumber,ticket);
        }
        return ticket;
    }
}

最后经窖,我們在看看客戶端如何使用簡單工廠復(fù)用共享的Ticket對象坡垫。


public class Client {

    public static void main(String[] args) {

        for (int trainNumber = 0; trainNumber < 10; trainNumber++) {
            for (int i = 0; i < 100; i++) {
                //原來的處理方式
                //Ticket ticket = new Ticket(trainNumber,from,to,seatOrder,userName);
                //ticket.print();
                //現(xiàn)在的處理方式將共同的屬性和特殊的屬性分離
                ITicket ticket = TicketFactory.getTicket(trainNumber);
                ticket.print(userName,seatOrder);
            }
        }
    }
}

總結(jié)

我在很多享原模式的文章中,都看到過這樣一種觀點:String常量池画侣、數(shù)據(jù)庫連接池冰悠、緩沖池等池化技術(shù)都使用了享原模式。
其實配乱,這種認(rèn)識是有誤的溉卓,因為他們混淆了完全復(fù)用對象和部分復(fù)用對象的差異。如:String常量搬泥,它復(fù)用的條件是字符和常量池中的字符完全一致桑寨;而享原模式只是復(fù)用對象的部分屬性,而且還要參數(shù)化外部狀態(tài)忿檩。
所以尉尾,不能將兩者等同看待,應(yīng)該區(qū)分他們之間的差異燥透。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沙咏,一起剝皮案震驚了整個濱河市辨图,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肢藐,老刑警劉巖故河,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吆豹,居然都是意外死亡鱼的,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門痘煤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凑阶,“玉大人,你說我怎么就攤上這事速勇∩卫” “怎么了坎拐?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵烦磁,是天一觀的道長。 經(jīng)常有香客問我哼勇,道長都伪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任积担,我火速辦了婚禮陨晶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帝璧。我一直安慰自己先誉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布的烁。 她就那樣靜靜地躺著褐耳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪渴庆。 梳的紋絲不亂的頭發(fā)上铃芦,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機(jī)與錄音襟雷,去河邊找鬼刃滓。 笑死,一個胖子當(dāng)著我的面吹牛耸弄,可吹牛的內(nèi)容都是我干的咧虎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼计呈,長吁一口氣:“原來是場噩夢啊……” “哼砰诵!你這毒婦竟也來了僚饭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胧砰,失蹤者是張志新(化名)和其女友劉穎鳍鸵,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尉间,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡偿乖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哲嘲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贪薪。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖眠副,靈堂內(nèi)的尸體忽然破棺而出画切,到底是詐尸還是另有隱情,我是刑警寧澤囱怕,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布霍弹,位于F島的核電站,受9級特大地震影響娃弓,放射性物質(zhì)發(fā)生泄漏典格。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一台丛、第九天 我趴在偏房一處隱蔽的房頂上張望耍缴。 院中可真熱鬧,春花似錦挽霉、人聲如沸防嗡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚁趁。三九已至,卻和暖如春硅蹦,著一層夾襖步出監(jiān)牢的瞬間荣德,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工童芹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留涮瞻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓假褪,卻偏偏與公主長得像署咽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

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