前言
來啦老鐵!
筆者正在學習常見的設(shè)計模式,且將設(shè)計模式系列學習文章歸入 “設(shè)計模式學習” 專題,趕快關(guān)注專題一起學習吧!
今天我們繼續(xù)學習:
-
單例模式
備注:筆者的學習資料大部分來源于:菜鳥教程;
學習路徑
- 單例模式簡介;
- 單例模式代碼實現(xiàn);
- 單例模式優(yōu)缺點分析;
- 單例模式使用場景介紹;
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é)果:

成功~
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. 單例模式使用場景介紹;
- 一個班級只有一個班主任。
- Windows 是多進程多線程的,在操作一個文件的時候,就不可避免地出現(xiàn)多個進程或線程同時操作一個文件的現(xiàn)象,所以所有文件的處理必須通過唯一的實例來進行。
- 一些設(shè)備管理器常常設(shè)計為單例模式,比如一個電腦有兩臺打印機,在輸出的時候就要處理不能兩臺打印機打印同一個文件。
- 要求生產(chǎn)唯一序列號。
- WEB 中的計數(shù)器,不用每次刷新都在數(shù)據(jù)庫里加一次,用單例先緩存起來。
- 創(chuàng)建的一個對象需要消耗的資源過多,比如 I/O 與數(shù)據(jù)庫的連接等。
其他參考文獻:https://zhuanlan.zhihu.com/p/37534850
如果本文對您有幫助,麻煩點贊、關(guān)注!
謝謝!



