大牧絮叨設計模式:單例模式

[TOC]

1、單例模式概述

單例模式(Singleton)[GOF95]是一種對象的創建模式,確保系統中使用了單例模式的類型只會存在一個實例對象,通過該對象給系統提供一致性的解決方案。

單例模式管理的類型,常規情況下具備如下特性

  • 單例類只能有一個實例
  • 單例類必須自己創建屬于自己的唯一實例對象。
  • 單例類必須給其他調用者提供自己的實例對象。

注意:在項目開發過程中,項目可能會存在需要限定多個實例的情況,所以單例模式是一個基礎模式,可以在此模式基礎上衍生多例模式

單例實現過程中,根據實例的創建時機區分為餓漢式單例懶漢式單例 兩種不同的操作方式,所謂餓漢式表示在類型定義時在類型中已經創建了對應的實例,其他調用者可以隨時調用獲取;所謂懶漢式是在類型定義時定義了單例的實現邏輯,但是只有調用者在使用該單例時,第一次調用時才會發生具體實例化過程,之后再使用過程中該實例就是唯一的提供服務的對象。

1.1、 核心組件

單例模式中,最核心的部分就是當前類型自己,為了符合需求中對于單例的使用要求,通常情況下我們會設計當前類型自己的靜態方法來獲取該類型的實例,并且當前類型不可實例化(構造方法私有化)。

  • 單例類(Singleton Class):單例模式的核心類型,和系統中的業務邏輯緊密關聯,實例的創建部分的處理由自身的靜態方法完成,對外只暴露包含業務功能的單個實例對象。
image.png

1.2、 優點缺陷

單例類的設計通常是基于某些系統功能的要求,一般情況下是基于功能上的限制或者業務上的要求,某一部分功能業務邏輯在整個系統平臺的運行周期中,必須是讓所有功能模塊共享的獨立一份,此時單例模式的應用能簡單開發過程中的業務邏輯的復雜度,但是注意單例模式并不是唯一的解決方案。

優點

  • 能有好的處理系統中獨立一份數據的共享和信息傳遞
  • 減少內存開銷,避免資源過度占用

缺點

  • 對于DIP原則以及OCP原則支持不友好,功能擴展不方便

2、 Java實現

2.1、餓漢式單例模式

餓漢式單例模式基礎代碼,如有功能或者業務邏輯上的需要,可以在此基礎上進行擴展。

/**
 * <p>項目文檔: TODO</p>
 *
 * @author 大牧
 * @version V1.0
 */
public class SingletonForHungry {

    // 1. 內部屬性中直接實例化當前對象
    private static SingletonForHungry myInstance = new SingletonForHungry();

    // 2. 構造方法私有化,屏蔽外界實例化途徑
    private SingletonForHungry () {}

    // 3. 提供獲取實例對象的靜態方法
    public static SingletonForHungry getInstance() {
        return myInstance;
    }

}

測試代碼,請獨立創建一個入口類進行測試

/**
 * <p>項目文檔: TODO</p>
 *
 * @author 大牧
 * @version V1.0
 */
public class SingletonMain {
    public static void main(String[] args) {
        // 測試代碼
        SingletonForHungry sf = SingletonForHungry.getInstance();
        System.out.println(sf); // SingletonForHungry@61bbe9ba

        SingletonForHungry sf2 = SingletonForHungry.getInstance();
        System.out.println(sf2); // SingletonForHungry@61bbe9ba

//        SingletonForHungry sf3 = new SingletonForHungry();// ERROR
//        System.out.println(sf3);
    }
}

2.2、 懶漢式單例模式

/**
 * <p>項目文檔: 懶漢式單例模式</p>
 *
 * @author 大牧
 * @version V1.0
 */
public class SingletonForLazy {

    // 1. 聲明一個存放實例對象的變量
    private static SingletonForLazy myInstance;

    // 2. 構造方法私有化
    private SingletonForLazy() {}

    // 3. 提供獲取當前類型實例對象的靜態方法
    public static SingletonForLazy getInstance() {
        myInstance = myInstance == null ? new SingletonForLazy():myInstance;
        return myInstance;
    }
}

測試代碼:

/**
 * <p>項目文檔: TODO</p>
 *
 * @author 大牧
 * @version V1.0
 */
public class SingletonMain {
    public static void main(String[] args) {
         // 懶漢式單例 測試代碼
        SingletonForLazy sl = SingletonForLazy.getInstance();
        System.out.println(sl); // SingletonForLazy@61bbe9ba

        SingletonForLazy sl2 = SingletonForLazy.getInstance();
        System.out.println(sl2); // SingletonForLazy@61bbe9ba

//        SingletonForLazy sl3 = new SingletonForLazy();// ERROR
//        System.out.println(sl3);
    }
}

注意:這里的單例模式,主要是業務上的限制,并不代表在代碼執行過程中外界不能干預,比如通過反射的方式實例化單例類的其他實例,但是根本上來說只要符合需求的模式下,單例模式能解決我們在項目中遇到的通用問題,也就是單例模式就是一種解決方案,會因不同的場景而進行不同的設計。

3、 Python實現

Python本身是弱類型語言,所以單例模式的實現手段較多,我們這里簡單給大家展示幾種操作方式。

3.1、 單實例操作

Python中的一切都是數據都是對象,包括類型的聲明定義也是一種對象,在操作過程中根據這一特性我們可以直接構建單例模式

singleton.py
-------------------------------------------
"""
單例模式
"""
# 實現1:單實例操作
class Singleton:
    pass

# 創建實例對象
singleton_instalce = Singleton()
# 刪除類定義
del Singleton
print(singleton_instalce) # <__main__.Singleton object at 0x10c221080>

singleton_instalce2 = Singleton() # NameError: name 'Singleoton' is not defined
print(singleton_instalce2)

上述代碼中,通過類型Singleton可以很方便的創建屬于該類型的對象singleton_instance,完成對象創建后刪除了類型的定義,在后續的代碼使用過程中,利用Python語言本身的特性,可以通過import方式使用該單例對象,其他Python模塊中可以按照如下方式引入使用

# 引入單例實例
from singleton import singleton_instance

3.2、 靜態方法

通過類型的靜態方法,提供一個獲取當前類型實例化的公共API,在業務上限制該實例的單例操作模式

# 實現2:靜態方法
import threading
class Singleton:

    _instance_lock = threading.Lock()

    @classmethod
    def get_instance(self, *args, **kwargs):
        """獲取當前實例的靜態方法"""
        if not hasattr(Singleton, "_my_instance"):
            # 線程鎖,保證多線程訪問安全
            with Singleton._instance_lock:
                Singleton._my_instance = Singleton()
        return Singleton._my_instance

singleton = Singleton.get_instance()
print(singleton) #<__main__.Singleton object at 0x102dc00f0>

singleton2 = Singleton.get_instance()
print(singleton2) #<__main__.Singleton object at 0x102dc00f0>

但是上述的單例操作中,不排除開發人員可能會通過類型直接構建對象的行為,代碼如下:

singleton3 = Singleton()
print(singleton3) #<__main__.Singleton object at 0x1012c00b8>

所以我們在這里操作的單例模式,描述為添加了業務限制的單例操作,在業務要求中如果要使用該類型的實例必須通過get_instance()方法進行獲取。

3.3、 __new__魔法方法

Python中的類型在實例化過程中,逐次調用了__new__(cls)進行實例的構建和__init__(self)進行數據的初始化操作,我們通過__new__(cls)魔法方法直接完成單例的處理,是Python中最常見的一種操作方式。

# 實現3:__new__()魔法方法
class Singleton:

    import threading
    _instance_lock = threading.Lock()

    def __init__(self, name):
        self.name = name

    def __new__(cls, *args, **kwargs):
        """構建方法"""
        if not hasattr(Singleton, "_instance"):
            with Singleton._instance_lock:
                Singleton._instance = object.__new__(cls)

        return Singleton._instance

# 分別創建兩個不同的實例
singleton = Singleton("tom")
singleton2 = Singleton("jerry")
# 查看兩個不同實例的屬性數據和內存地址
print(singleton, singleton.name) # <__main__.Singleton object at 0x101d13128> jerry
print(singleton2, singleton2.name) # <__main__.Singleton object at 0x101d13128> jerry

通過上述代碼可以看到,不同時間創建的對象,最終的數據都是一致的,包括在內存中的地址,也就是表明了當前的多個實例是同一個內存中的的對象數據,因為添加了線程鎖,所以該單例模式即使在多線程環境下也是安全的。

3.4、 裝飾器實現

上述的幾種實現方式都非常有好,尤其是通過__new__(cls)的實現操作,但是在實際開發過程中代碼的復用性并不是非常好,我們可以使用Python中的裝飾器完成單例模式的檢查約束,同時也保證了代碼的復用性。但是裝飾器的操作模式并不是全部適用,如果一個系統中出現大量的單例對象(當然基本不可能出現),裝飾器的操作模式就會特別消耗系統資源,廢話不多直接看實現


# 實現4:裝飾器操作
def singleton(cls):
    """單例類裝飾器"""
    # 聲明一個存儲對象的字典
    _instance = dict()

    def _singleton(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)

        return _instance
    return _singleton


@singleton
class Single:
    def __init__(self, name):
        self.name = name

s1 = Single("tom")
s2 = Single("jerry")
print(s1) # <class '__main__.Single'>:<__main__.Single object at 0x10b3b10b8>
print(s2) # <class '__main__.Single'>:<__main__.Single object at 0x10b3b10b8>

由于Python語言本身的靈活性,基于python的實現方式比較多,大家可以在實現過程中自行參考。

4、 Go實現

Go語言是通過結構體來定義類型的,所以單例的操作就比較靈活了,這里介紹一種簡單的實現操作模式

package main

import (
    "fmt"
    "sync"
)

type Singleton struct {}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        fmt.Println("instance invoking....")
        instance = &Singleton{}
    })
    return instance
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,800評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內容