創(chuàng)建型設(shè)計(jì)模式——單例模式

面向?qū)ο蟮牧笤瓌t

  • 單一職責(zé)原則 Single Responsibility Principle
    一個類中應(yīng)該是一組相關(guān)性很高的函數(shù)俱饿、數(shù)據(jù)的封裝寥殖。兩個完全不一樣的功能就不應(yīng)該放在一個類中。

  • 開閉原則 Open Close Principle
    軟件中的對象(類账胧、模塊窝剖、函數(shù)等)應(yīng)該對于擴(kuò)展是開放的棠隐,但是對于修改是封閉的。
    在軟件的生命周期內(nèi)轻黑,因?yàn)樽兓簟⑸墶⒕S護(hù)等原因需要對軟件原有的代碼進(jìn)行修改時苔悦,可能會將錯誤引入原本已經(jīng)經(jīng)過測試的舊代碼中轩褐,破壞原有系統(tǒng)。因此當(dāng)軟件需要變化時玖详,我們應(yīng)該盡量通過擴(kuò)展的方式來實(shí)現(xiàn)變化把介,而不是通過修改已有的代碼來實(shí)現(xiàn)勤讽。盡量,說明OCP原則并不是說絕對不能修改原始類的拗踢,當(dāng)原始類不好的時候應(yīng)該盡早的進(jìn)行重構(gòu)脚牍。

  • 里氏替換原則 Liskov Substitution Principle
    所有引用基類的地方,必須能透明地使用其子類的對象巢墅。里氏替換原則就是依賴于繼承和多態(tài)這兩大特性诸狭。
    開閉原則和里氏替換原則往往是相互依賴的,通過里氏替換來達(dá)到對擴(kuò)展開放君纫,對修改 封閉的效果驯遇。

  • 依賴倒置原則 Dependence Inversion Principle
    一種特定的解耦形式,使得高層次的模塊不依賴底層次的模塊蓄髓。具體而言即:1)高層模塊不應(yīng)該依賴低層模塊叉庐,兩者都應(yīng)該依賴其抽象;2)抽象不應(yīng)該依賴細(xì)節(jié)会喝;3)細(xì)節(jié)應(yīng)該依賴抽象陡叠。
    在Java語言中,一般抽象是指接口或者抽象類肢执,兩者都無法被實(shí)例化枉阵。細(xì)節(jié)是指實(shí)現(xiàn)類,實(shí)現(xiàn)接口或繼承抽象類而產(chǎn)生的類就是細(xì)節(jié)预茄。
    模塊間的依賴通過抽象發(fā)生兴溜,實(shí)現(xiàn)類之間不發(fā)生直接的依賴關(guān)系,其依賴關(guān)系是通過接口或抽象類產(chǎn)生的反璃。

  • 接口隔離原則 Interface Segregation Principle
    客戶端不應(yīng)該依賴它不需要的接口昵慌。目的是系統(tǒng)解耦,從而更容易重構(gòu)淮蜈、更改和重新部署斋攀。
    上述是面向?qū)ο缶幊痰?個基本原則。當(dāng)這些原則被一起應(yīng)用時梧田,它們使得一個軟件系統(tǒng)更清晰淳蔼、簡單,最大程度地?fù)肀ё兓?/p>

  • 迪米特原則 最少知識原則 Least Knowledge Principle
    一個類應(yīng)該對自己需要耦合或調(diào)用的類知道得最少裁眯,類的內(nèi)部如何實(shí)現(xiàn)與調(diào)用者或者依賴者沒關(guān)系鹉梨,調(diào)用者或者依賴者只需要知道它需要的方法即可,其他的可一概不管穿稳。


單例模式是應(yīng)用最廣的模式之一存皂。

定義

確保某個類只有一個實(shí)例,而且自行實(shí)例化并向整個系統(tǒng)提供這個實(shí)例。

以上可知單例類的特點(diǎn):

  • 只能有一個實(shí)例
  • 必須自己創(chuàng)建自己的唯一實(shí)例
  • 必須給其他對象(整個系統(tǒng))提供這一實(shí)例

使用場景

當(dāng)這個類的對象在多個地方創(chuàng)建的時候旦袋,使得內(nèi)部的方法多次調(diào)用骤菠,但是我們希望只要一個對象操作這個方法,或者不希望多個地方同時調(diào)用這個方法疤孕,我們需要保持這個方法的單一性質(zhì)商乎,我們就用單例模式。

比如:訪問IO和數(shù)據(jù)庫資源祭阀、單一彈框等鹉戚。

實(shí)現(xiàn)單例模式的關(guān)鍵點(diǎn)

  • 構(gòu)造函數(shù)不對外開放,一般為Private
  • 通過一個靜態(tài)方法或者枚舉返回單例類對象
  • 確保單例類的對象有且僅有一個专控,尤其是在多線程環(huán)境下
  • 確保單例類對象在反序列化時不會重新構(gòu)建對象
單例模式UML類圖

單例模式的實(shí)現(xiàn)方法

1.懶漢模式
//懶漢式單例類.在第一次調(diào)用的時候?qū)嵗约?public class LHS_Singleton {

    private static LHS_Singleton instance;

    private LHS_Singleton() {
    }

    //靜態(tài)工廠方法
    public static synchronized LHS_Singleton getInstance() {
        if (instance == null) {
            instance = new LHS_Singleton();
        }
        return instance;
    }
}

getInstance()方法中用到了synchronized同步鎖抹凳,保證了線程安全。
懶漢式是典型的時間換空間

  • 優(yōu)點(diǎn):單例只有在使用的時候才會被實(shí)例化伦腐,一定程度上節(jié)約了資源却桶。
  • 缺點(diǎn):第一次加載的時候需要及時進(jìn)行實(shí)例化,最大問題在于每次調(diào)用都要進(jìn)行同步蔗牡,會造成不必要的同步開銷,降低整個訪問速度嗅剖,且每次都需要進(jìn)行判斷是否需要創(chuàng)建實(shí)例辩越。
2.DCL雙重檢查加鎖模式
public class DCL_Singleton {
    private volatile static DCL_Singleton singleton = null;

    private DCL_Singleton() {
    }

    public static DCL_Singleton getSingleton() {
        //先檢查實(shí)例是否存在,如果不存在才進(jìn)入下面的同步塊
        if (singleton == null) {
            //同步塊信粮,線程安全的創(chuàng)建實(shí)例
            synchronized (DCL_Singleton.class) {
                //再次檢查實(shí)例是否存在黔攒,如果不存在才真正的創(chuàng)建實(shí)例
                if (singleton == null) {
                    singleton = new DCL_Singleton();
                }
            }
        }
        return singleton;
    }
}

與懶漢式不同在于,在getInstance()方法中不用再進(jìn)行同步鎖强缘。第一個判斷singleton == null主要是為了避免不必要的同步督惰,第二次判斷則是為了singleton = null的時候在線程安全的情況下創(chuàng)建實(shí)例。

  • 優(yōu)點(diǎn):資源利用率高旅掂,既能夠在需要的時候才初始化單例赏胚,又能保證線程安全,且單例對象初始化后調(diào)用getInstance()不再進(jìn)行同步鎖商虐。
  • 缺點(diǎn):第一次加載反應(yīng)稍慢觉阅,由于Java內(nèi)存模型原因偶爾會失敗,高并發(fā)情況下有一定缺陷秘车。
  • 注:由于volatile關(guān)鍵字可能會屏蔽掉虛擬機(jī)中一些必要的代碼優(yōu)化典勇,所以運(yùn)行效率并不是很高。因此雖然可以使用“雙重檢查加鎖”機(jī)制來實(shí)現(xiàn)線程安全的單例叮趴,但并不建議大量采用割笙,可以根據(jù)情況來選用。
3.靜態(tài)內(nèi)部類模式
public class JTNBL_Singleton {
    private JTNBL_Singleton() {
    }

    public static JTNBL_Singleton getInstanse() {
        return SingletonHolder.instance;
    }

    /**
     *    類級的內(nèi)部類眯亦,也就是靜態(tài)的成員式內(nèi)部類伤溉,該內(nèi)部類的實(shí)例與外部類的實(shí)例
     *    沒有綁定關(guān)系般码,而且只有被調(diào)用到時才會裝載,從而實(shí)現(xiàn)了延遲加載谈火。
     */
    private static class SingletonHolder {
        /**
         * 靜態(tài)初始化器侈询,由JVM來保證線程安全
         */
        private static final JTNBL_Singleton instance = new JTNBL_Singleton();
    }
}

當(dāng)getInstance()方法第一次被調(diào)用的時候,它第一次讀取SingletonHolder.instance糯耍,會使SingletonHolder類得到初始化扔字;而這個類在裝載并被初始化的時候,會初始化它的靜態(tài)域温技,從而創(chuàng)建JTNBL_Singleton的實(shí)例革为,由于是靜態(tài)的域,因此只會在虛擬機(jī)加載類的時候初始化一次舵鳞,并由虛擬機(jī)來保證它的線程安全性震檩。

優(yōu)點(diǎn):這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性蜓堕,同時也延遲了單例的實(shí)例化抛虏。

在上述的幾種實(shí)現(xiàn)單例模式的情況中,有一個情況下他們會出現(xiàn)重新創(chuàng)建對象的情況套才,那就是反序列化迂猴。如果要杜絕單例對象在被反序列化時重新生成對象,那么必須加入下面的方法:

private Object readResolve() throws ObjectStreamException {
        return instance;
    }
4.枚舉單例

對于枚舉背伴,則不存在反序列化問題沸毁,因?yàn)榧词狗葱蛄谢杜e單例也不會重新生成新的實(shí)例。

public enum MJ_Singleton {
    /**
     * 定義一個枚舉的元素傻寂,它就代表了MJ_Singleton的一個實(shí)例息尺。
     */
    INSTANCE;

    /**
     * 單例可以有自己的操作
     */
    public void doSomething() {
        System.out.println("Hello World");
    }
}

枚舉在JAVA中與普通的類一樣,不僅能有字段疾掰,還能有自己的方法搂誉,最重要的是默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,并且在任何情況下它都是一個單例静檬。

按照《高效Java 第二版》中的說法:單元素的枚舉類型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法勒葱。用枚舉來實(shí)現(xiàn)單例非常簡單,只需要編寫一個包含單個元素的枚舉類型即可巴柿。

5.使用容器實(shí)現(xiàn)單例模式
public class RQ_Singleton {
    private static Map<String, Object> objectMap = new HashMap<>();

    private RQ_Singleton() {
    }

    public static void registerService(String key, Object instance) {
        if (!objectMap.containsKey(key)) {
            objectMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objectMap.get(key);
    }
}

在程序初始凛虽,將多種單例類型注入到一個統(tǒng)一的單例管理類中,通過key來獲取對應(yīng)類型的對象广恢。

這種方式使得我們可以管理多種類型的單例凯旋,并且在使用時可以通過統(tǒng)一的接口進(jìn)行獲取操作,降低了耦合度,對用戶隱藏了具體實(shí)現(xiàn)至非。

6.餓漢模式
public class EHS_Singleton {
    // 在這個類被加載的時候钠署,靜態(tài)變量single會被初始化,
    // 此時類的私有構(gòu)造子會被調(diào)用荒椭。這時單例類的唯一實(shí)例就被構(gòu)造出來了谐鼎。
    private static EHS_Singleton instance = new EHS_Singleton();

    private EHS_Singleton() {
    }

    public static EHS_Singleton getInstance() {
        return instance;
    }
}

餓漢式其實(shí)是一種比較形象的稱謂。既然餓趣惠,那么在創(chuàng)建對象實(shí)例的時候就比較著急狸棍,餓了嘛,于是在裝載類的時候就創(chuàng)建對象實(shí)例味悄。

餓漢式是典型的空間換取時間草戈,當(dāng)類裝載時就會創(chuàng)建類的實(shí)例,不管你用不用侍瑟,先創(chuàng)建出來唐片,然后每次調(diào)用的時候,就不需要再判斷涨颜,節(jié)省了運(yùn)行時間费韭。

單例模式的優(yōu)點(diǎn)

  • 在內(nèi)存中只有一個實(shí)例,減少了內(nèi)存開支庭瑰,特別是對于一個對象需要頻繁地創(chuàng)建揽思、銷毀時,而且創(chuàng)建或銷毀時性能又無法優(yōu)化见擦,單例模式的優(yōu)勢便非常明顯。
  • 當(dāng)一個對象的產(chǎn)生需要比較多的資源時羹令,如讀取配置鲤屡、產(chǎn)生其他依賴對象時,則可以通過在應(yīng)用啟動時直接產(chǎn)生一個單例對象福侈,然后永久駐留內(nèi)存的方式來解決酒来。
  • 單例模式可以避免對資源的多重占用,例如寫文件操作肪凛,避免了對同一個資源文件的同時寫操作堰汉。
  • 單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源訪問伟墙,例如可以設(shè)計(jì)一個單例類翘鸭,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。

單例模式的缺點(diǎn)

  • 單例模式一般沒有接口戳葵,擴(kuò)展很困難就乓,若要擴(kuò)展,除了修改代碼基本上沒有第二種途徑可實(shí)現(xiàn)
  • 單例對象如果持有Context,那么很容易引發(fā)內(nèi)存泄漏生蚁,此時需要注意傳遞給單例對象的Context最好是Application Context



引用:
Android 源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
Java設(shè)計(jì)模式之單例模式及在Android中的重要使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末噩翠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子邦投,更是在濱河造成了極大的恐慌伤锚,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件志衣,死亡現(xiàn)場離奇詭異屯援,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蠢涝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門玄呛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人和二,你說我怎么就攤上這事徘铝。” “怎么了惯吕?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵惕它,是天一觀的道長。 經(jīng)常有香客問我废登,道長淹魄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任堡距,我火速辦了婚禮甲锡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羽戒。我一直安慰自己缤沦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布易稠。 她就那樣靜靜地躺著缸废,像睡著了一般。 火紅的嫁衣襯著肌膚如雪驶社。 梳的紋絲不亂的頭發(fā)上企量,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天,我揣著相機(jī)與錄音亡电,去河邊找鬼届巩。 笑死,一個胖子當(dāng)著我的面吹牛份乒,可吹牛的內(nèi)容都是我干的姆泻。 我是一名探鬼主播零酪,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拇勃!你這毒婦竟也來了四苇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤方咆,失蹤者是張志新(化名)和其女友劉穎月腋,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓣赂,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榆骚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了煌集。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妓肢。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖苫纤,靈堂內(nèi)的尸體忽然破棺而出碉钠,到底是詐尸還是另有隱情,我是刑警寧澤卷拘,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布喊废,位于F島的核電站,受9級特大地震影響栗弟,放射性物質(zhì)發(fā)生泄漏污筷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一乍赫、第九天 我趴在偏房一處隱蔽的房頂上張望瓣蛀。 院中可真熱鬧孽尽,春花似錦馆匿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至溪猿,卻和暖如春钩杰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诊县。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工讲弄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人依痊。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓避除,卻偏偏與公主長得像怎披,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瓶摆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354