單例模式(Single Pattern)

說明:本文為《設(shè)計(jì)模式之禪》的閱讀筆記,主要總結(jié)精華和記錄自己的部分理解。文中代碼部分主要由Kotlin實(shí)現(xiàn)。

1. 定義

Ensure a class has only one instance, and provide a global point of access to it.
確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。

單例模式通用類圖

代碼清單1 單例模式

// Kotlin中實(shí)現(xiàn)單例模式就比較簡單,只需要定義一個(gè)object對(duì)象表達(dá)式即可,無需手動(dòng)設(shè)置構(gòu)造器私有化和提供全局的訪問點(diǎn)
object Singleton {
    fun doSomething() {
        println("do some thing")
    }

    private fun readResult(): Any {
        return Singleton
    }
}


// 在Kotlin中使用Singleton
fun main(args: Array<String>) {
    println("Hello World!")
    Singleton.doSomething() // 像調(diào)用靜態(tài)方法一樣,調(diào)用單例類中的方法
}

代碼清單2 Singleton 反編譯成 Java 代碼

public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE;

   public final void doSomething() {
      String var1 = "do some thing";
      System.out.println(var1);
   }

   private final Object readResult() {
      return INSTANCE; // 可以看到readResult方法直接返回了INSTANCE而不是創(chuàng)建新的實(shí)例
   }

   private Singleton() {
   }

   static {
       // 靜態(tài)代碼塊初始化Singleton實(shí)例
       // 只要Singleton被加載了,靜態(tài)代碼塊就會(huì)被調(diào)用,Singleton實(shí)例就會(huì)被創(chuàng)建,并賦值給INSTANCE
      Singleton var0 = new Singleton();
      INSTANCE = var0;
   }
}

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

  • 由于單例模式在內(nèi)存中只有一個(gè)實(shí)例,減少了內(nèi)存開支
    特別是一個(gè)對(duì)象需要頻繁地創(chuàng)建、銷毀時(shí),而且創(chuàng)建或銷毀時(shí)性能又無法優(yōu)化,單例模式的優(yōu)勢就非常明顯。

  • 由于單例模式只生成一個(gè)實(shí)例,所以減少了系統(tǒng)的性能開銷
    當(dāng)一個(gè)對(duì)象的產(chǎn)生需要比較多的資源時(shí),如讀取配置、產(chǎn)生其他依賴對(duì)象時(shí),則可以通過在應(yīng)用啟動(dòng)時(shí)直接產(chǎn)生一個(gè)單例對(duì)象,然后用永久駐留內(nèi)存的方式來解決(在Java EE中采用單例模式時(shí)需要注意JVM垃圾回收機(jī)制)。

  • 單例模式可以避免對(duì)資源的多重占用
    例如一個(gè)寫文件動(dòng)作,由于只有一個(gè)實(shí)例存在內(nèi)存中,避免對(duì)同一個(gè)資源文件的同時(shí)寫操作。

  • 單例模式可以在系統(tǒng)設(shè)置全局的訪問點(diǎn),優(yōu)化和共享資源訪問
    例如可以設(shè)計(jì)一個(gè)單例類,負(fù)責(zé)所有數(shù)據(jù)表的映射處理。

3. 缺點(diǎn)

  • 單例模式一般沒有接口,擴(kuò)展很困難,若要擴(kuò)展,除了修改代碼基本上沒有第二種途徑可以實(shí)現(xiàn)。
    單例模式為什么不能增加接口呢?因?yàn)榻涌趯?duì)單例模式是沒有任何意義的,它要求“自行實(shí)例化”,并且提供單一實(shí)例,接口或抽象類是不可能被實(shí)例化的。當(dāng)然,在特殊情況下,單例模式可以實(shí)現(xiàn)接口、被繼承等,需要在系統(tǒng)開發(fā)中根據(jù)環(huán)境判斷。

  • 單例模式對(duì)測試不利
    在并行開發(fā)環(huán)境中,如果單例模式?jīng)]有完成,是不能進(jìn)行測試的,沒有接口也不能使用mock的方式虛擬一個(gè)對(duì)象。

  • 單例模式與單一職責(zé)原則有沖突。
    一個(gè)類應(yīng)該只實(shí)現(xiàn)一個(gè)邏輯,而不關(guān)心它是否是單例的,是不是要單例取決于環(huán)境,單例模式把“要單例”和業(yè)務(wù)邏輯融合在一個(gè)類中。

4. 單例模式的使用場景

  • 要求生成唯一序列號(hào)的環(huán)境
  • 在整個(gè)項(xiàng)目中需要一個(gè)共享訪問點(diǎn)或共享數(shù)據(jù)
    例如一個(gè)Web頁面上的計(jì)數(shù)器,可以不用把每次刷新都記錄到數(shù)據(jù)庫中,使用單例模式保持計(jì)數(shù)器的值,并確保是線程安全的。
  • 創(chuàng)建一個(gè)對(duì)象需要消耗的資源過多
    如要訪問IO和數(shù)據(jù)庫等資源。
  • 需要定義大量的靜態(tài)常量和靜態(tài)方法(如工具類)的環(huán)境,可以采用單例模式(當(dāng)然,也可以直接聲明為static的方式)。

5. 注意事項(xiàng)

  • 在高并發(fā)情況下,請(qǐng)注意單例模式的線程同步問題
    單例模式有幾種不同的實(shí)現(xiàn)方式,有的不會(huì)出現(xiàn)產(chǎn)生多個(gè)實(shí)例的情況,但是有的實(shí)現(xiàn)方式就需要考慮線程同步。 (懶漢式單例模式可能存在多個(gè)線程同時(shí)調(diào)用單例模式中的方法創(chuàng)建實(shí)例,就會(huì)產(chǎn)生兩個(gè)實(shí)例。)

代碼清單3 線程安全的懶漢式單例

class LazySingleton private constructor() {

    companion object {
        private var lazyInstance: LazySingleton? = null
            get() {
                return field ?: LazySingleton()
            }

        @JvmStatic
        @Synchronized // 添加synchronized同步鎖
        fun getInstance(): LazySingleton {
            return requireNotNull(lazyInstance)
        }
    }

    fun doSomething() {
        println("do some thing")
    }
}


//在Kotlin中調(diào)用
fun main(args: Array<String>) {
    LazySingleton.getInstance()
}
  • 需要考慮對(duì)象的復(fù)制情況
    在Java中,對(duì)象默認(rèn)是不可以被復(fù)制的,若實(shí)現(xiàn)了Cloneable接口,并實(shí)現(xiàn)了clone方法,則可以直接通過對(duì)象復(fù)制方式創(chuàng)建一個(gè)新對(duì)象,對(duì)象復(fù)制是不用調(diào)用類的構(gòu)造函數(shù),因此即使是私有的構(gòu)造函數(shù),對(duì)象仍然可以被復(fù)制。在一般情況下,類復(fù)制的情況不需要考慮,很少會(huì)出現(xiàn)一個(gè)單例類會(huì)主動(dòng)要求被復(fù)制的情況,解決該問題的最好方法就是單例類不要實(shí)現(xiàn)Cloneable接口。

6. 單例模式的擴(kuò)展

  • 有上線的多例模式

需要產(chǎn)生固定數(shù)量對(duì)象的模式

采用有上限的多例模式,我們可以在設(shè)計(jì)時(shí)決定在內(nèi)存中有多少個(gè)實(shí)例,方便系統(tǒng)進(jìn)行擴(kuò)展,修正單例可能存在的性能問題,提供系統(tǒng)的響應(yīng)速度。例如讀取文件,我們可以在系統(tǒng)啟動(dòng)時(shí)完成初始化工作,在內(nèi)存中啟動(dòng)固定數(shù)量的reader實(shí)例,然后在需要讀取文件時(shí)就可以快速響應(yīng)。

附1:思維導(dǎo)圖


單例模式(Single Pattern)

附2:代碼實(shí)現(xiàn) https://github.com/ooxiaoyan/singlepattern.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容