設(shè)計模式:單例模式

前言

來啦老鐵!

筆者正在學習常見的設(shè)計模式,且將設(shè)計模式系列學習文章歸入 “設(shè)計模式學習” 專題,趕快關(guān)注專題一起學習吧!

今天我們繼續(xù)學習:

  • 單例模式

備注:筆者的學習資料大部分來源于:菜鳥教程;

學習路徑

  1. 單例模式簡介;
  2. 單例模式代碼實現(xiàn);
  3. 單例模式優(yōu)缺點分析;
  4. 單例模式使用場景介紹;

1. 單例模式簡介;

單例模式(Singleton Pattern)是 Java 中最簡單的設(shè)計模式之一。這種類型的設(shè)計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

這種模式涉及到一個單一的類,該類負責創(chuàng)建自己的對象,同時確保只有單個對象被創(chuàng)建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。

  • 意圖:保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

  • 主要解決:
    一個全局使用的類頻繁地創(chuàng)建與銷毀。

  • 何時使用:
    當您想控制實例數(shù)目,節(jié)省系統(tǒng)資源的時候。

  • 如何解決:
    判斷系統(tǒng)是否已經(jīng)有這個單例,如果有則返回,如果沒有則創(chuàng)建。

  • 關(guān)鍵代碼:
    構(gòu)造函數(shù)是私有的。

2. 單例模式代碼實現(xiàn);

雖然單例模式很 Java 的樣子,單其實其他語言一樣能用、有用,我們還是從 python 的角度來看下單例模式怎么用代碼來實現(xiàn)吧~

據(jù)了解,python 中有幾種方式可以實現(xiàn)單例模式:
1. 使用模塊實現(xiàn)單例;
2. 使用函數(shù)裝飾器實現(xiàn)單例;
3. 使用類裝飾器實現(xiàn)單例;
4. 使用 new 關(guān)鍵字實現(xiàn)單例;
5. 使用 metaclass 實現(xiàn)單例;

1). 使用模塊實現(xiàn)單例;

python的模塊就是天然的單例模式,因為模塊在第一次導入的時候,會生成.pyc文件,當?shù)诙螌氲臅r候,就會直接加載.pyc文件,而不是再次執(zhí)行模塊代碼.如果我們把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個模塊中,就可以獲得一個單例對象了。

作者:莫辜負自己的一世韶光
鏈接:http://www.itdecent.cn/p/6a1690f0dd00
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

  • 創(chuàng)建一個類,例如 Database 類;
import time


class Database:
    def __init__(self):
        time.sleep(1)
        pass

    def get_mysql(self):
        print("get mysql...")


database = Database()

  • 使用 Database 類演示單例模式;
from database import database


def test():
    database.get_mysql()
    print("id:", id(database))
    database.get_mysql()
    print("id:", id(database))


if __name__ == '__main__':
    test()

  • 結(jié)果:


    單例模式演示

從打印出的對象 id 可以發(fā)現(xiàn),該方式多次使用 database 對象時未創(chuàng)建多個 database 對象,符合單例模式;

2). 使用函數(shù)裝飾器實現(xiàn)單例;
  • 創(chuàng)建一個類,如 Car 類,并用函數(shù)裝飾器完成單例模式;
def singleton(cls):
    _instance = {}

    def fn():
        if cls not in _instance:
            # 實例化類,并用字典存儲
            _instance[cls] = cls()
        # 返回字典中存儲的類
        return _instance[cls]

    return fn


@singleton
class Car:
    def __init__(self):
        pass

    def get_bmw(self):
        print("get BMW...")


if __name__ == "__main__":
    car_1 = Car()
    car_1.get_bmw()
    print(id(car_1))

    car_2 = Car()
    car_2.get_bmw()
    print(id(car_2))

  • 使用函數(shù)裝飾器實現(xiàn)的單例模式;

結(jié)果:


函數(shù)裝飾器實現(xiàn)的單例模式

成功~

3). 使用類裝飾器實現(xiàn)單例;
  • 創(chuàng)建一個類,如 Phone 類:
class Singleton:
    def __init__(self, cls):
        self._cls = cls
        self._instance = {}

    def __call__(self):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class Phone:
    def __init__(self):
        pass

    def get_huawei(self):
        print("Hua Wei...")


if __name__ == "__main__":
    phone_1 = Phone()
    phone_1.get_huawei()
    print(id(phone_1))

    phone_2 = Phone()
    phone_2.get_huawei()
    print(id(phone_2))

  • 使用類裝飾器實現(xiàn)的單例模式;
    結(jié)果:


    類裝飾器實現(xiàn)的單例模式

與函數(shù)裝飾器方式類似,成功~

4). 使用 new 關(guān)鍵字實現(xiàn)單例;
  • 創(chuàng)建一個類,如 School 類:
class School(object):
    _instance = None

    def __init__(self):
        pass

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance

    def get_qinghua(self):
        print("qing hua...")


if __name__ == '__main__':
    school_1 = School()
    school_1.get_qinghua()
    print(id(school_1))

    school_2 = School()
    school_2.get_qinghua()
    print(id(school_2))

  • 使用 new 關(guān)鍵字實現(xiàn)的單例模式;
    結(jié)果:
    __new__ 關(guān)鍵字實現(xiàn)的單例模式

成功~

5). 使用 metaclass 實現(xiàn)單例;
  • 了解使用 type 創(chuàng)造類的方法;
    在繼續(xù)實現(xiàn)之前,我們需要了解一個 python 知識點,即使用 type 創(chuàng)造類的方法,其例子類似:
def fn(self):
    # self 可以為任意其他字符,這里只是一個類似占位符的東西
    print("this is a test")


if __name__ == "__main__":
    # clazz 可以為其他任意字符
    # "fn" 為可被訪問的方法名,也可以為其他任意名字,"fn" 與 fn 可以名字不同
    clazz = type("clazz", (), {"fn": fn})
    test_class = clazz()
    test_class.fn()

這樣,我們就會用 type 創(chuàng)建類了,筆者也是第一次使用這種方式,只能說 python 還是有很多隱藏的知識點呀~

  • 創(chuàng)建一個類,如 Color 類:
class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Color(metaclass=Singleton):
    def __init__(self):
        pass

    def get_blue(self):
        print("get blue color...")


if __name__ == "__main__":
    color_1 = Color()
    color_1.get_blue()
    print(id(color_1))

    color_2 = Color()
    color_2.get_blue()
    print(id(color_2))

  • 使用 mataclass 實現(xiàn)單例模式;
    結(jié)果:


    mataclass 實現(xiàn)單例模式

成功~

注意,以上 5 種實現(xiàn)單例模式的方式,除了第 1 種在多線程場景下,能確保只有 1 個對象被創(chuàng)建,其他的均不能確保只有 1 個對象被創(chuàng)建,“單例”得還是不夠,因此,如果在多線程場景下,則需要給對象實例化的時候加鎖,例如:

import threading
from time import sleep


class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        with Color.__lock__:
            if cls not in cls._instances:
                cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]


class Color(metaclass=Singleton):
    __lock__ = threading.Lock()

    def __init__(self):
        sleep(1)
        pass

    def get_blue(self):
        print("get blue color...")


def worker():
    color = Color()
    print(id(color), "\n")


if __name__ == "__main__":
    # color_1 = Color()
    # color_1.get_blue()
    # print(id(color_1))
    #
    # color_2 = Color()
    # color_2.get_blue()
    # print(id(color_2))

    task_list = []
    for i in range(5):
        t = threading.Thread(target=worker)
        task_list.append(t)

    for t in task_list:
        t.start()

    for t in task_list:
        t.join()

或者使用裝飾器做一個鎖,這樣就能做到“全局”只有一個實例了~

3. 單例模式優(yōu)缺點分析;

優(yōu)點:
1、在內(nèi)存里只有一個實例,減少了內(nèi)存的開銷,尤其是頻繁的創(chuàng)建和銷毀實例(比如管理學院首頁頁面緩存)。
2、避免對資源的多重占用(比如寫文件操作)。

缺點:沒有接口,不能繼承,與單一職責原則沖突,一個類應(yīng)該只關(guān)心內(nèi)部邏輯,而不關(guān)心外面怎么樣來實例化。

4. 單例模式使用場景介紹;

  1. 一個班級只有一個班主任。
  2. Windows 是多進程多線程的,在操作一個文件的時候,就不可避免地出現(xiàn)多個進程或線程同時操作一個文件的現(xiàn)象,所以所有文件的處理必須通過唯一的實例來進行。
  3. 一些設(shè)備管理器常常設(shè)計為單例模式,比如一個電腦有兩臺打印機,在輸出的時候就要處理不能兩臺打印機打印同一個文件。
  4. 要求生產(chǎn)唯一序列號。
  5. WEB 中的計數(shù)器,不用每次刷新都在數(shù)據(jù)庫里加一次,用單例先緩存起來。
  6. 創(chuàng)建的一個對象需要消耗的資源過多,比如 I/O 與數(shù)據(jù)庫的連接等。

其他參考文獻:https://zhuanlan.zhihu.com/p/37534850

如果本文對您有幫助,麻煩點贊、關(guān)注!

謝謝!

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

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

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