為何要有 Singleton ?
重要性無需多言,我們在專案中經常有「要一個程式全域性的變數(記憶體塊)」的需求,而且單例模式是幾種設計模式中最容易的。
偷懶且有用的做法:模組級別常量
我經常使用這種方式,因為簡單且不易出錯。
眾所周知,Python 的 module 概念,是一個天然的 Singleton。而且 Python 是多正規化語言,可以不必像 Java 那樣使用 class 去處理這件事情,在 module 級別定義一個常量,是一種很自然的想法。
程式碼如下:
- 定義方
singleton.py
class _MySingleton(object):
"""
我們使用下劃線開頭, 告誡呼叫者, 不要直接 new
也不要來訪問這個class
"""
def __init__(self, name, age):
self._name = name
self._age = age
def print_name(self):
print(self._name)
# 可以定製多個全域性例項
S1 = _MySingleton(`s1`, 22)
S2 = _MySingleton(`s2`, 11)
複製程式碼
- 呼叫方
caller.py
from singleton import S1
# 盡情使用S1( 在任意點import 都可以 ), 它是全域性唯一的!
複製程式碼
正規做法:超程式設計
有些人不喜歡上面那種做法,他們認為「破壞了程式碼的純粹性」,這個時候我們可以使用超程式設計的方式,讓我們更近一步。
這是從 Python Cookbook 摘取出來的程式碼。
- 元類基類
class SingletonMetaclass(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super(SingletonMetaclass, self).__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
複製程式碼
- 繼承了元類基類的類
class SpamSingleton(metaclass=SingletonMetaclass):
"""注意: 根據Singleton的定義, 建構函式一般需要使用預設的建構函式"""
def get_addr(self):
return id(self)
複製程式碼
- 呼叫方
from singleton import SpamSingleton
s1 = SpamSingleton()
s2 = SpamSingleton()
pritn(id(s1), id(s2)) # 記憶體地址是一樣的
複製程式碼
老實說,理解 SingletonMetaclass 的作用過程還是有點困難的,我花了好久才搞清楚上面各個方法的呼叫流程。
不過 SingletonMetaclass 的作用也是巨大的,我們定義將其放入一個 base.py 的檔案中,任何時刻我們想要定義某個 Singleton class,直接從其繼承即可,簡單且方便。
比較 Hack 的做法:Borg 模式
不多說,搜尋引擎搜出來的結果,全都是推薦這種做法,讓人以為這是「主流做法」(其實並不是)。
這種做法,修改了 Singleton 的定義,即:所有變數共享一個記憶體塊,但是這個記憶體塊的內容是可變化的。然後通過將例項的 __dict__
方法重定向 class 的 __dict__
方法,以達到其目的。
但是這種做法不是很符合我對 Singleton的感知,即:全域性唯一,且其內容最好也不要變化。所以在實際開發中,並不喜歡這種做法。
更進一步
通常來說,更好的方法則是:在應用程式碼之外引入一個Manager,讓這個Manager來為我們建立和管理單例。這種做法也很普遍,就是我們通常所說的「依賴注入框架」。
如果使用 Spring,那麼一切都是很美妙的;如果沒有使用 Spring,我經常使用 Guice 來做我的依賴注入框架。
不過 Python 社群貌似對「依賴注入框架」、「IoC容器」等等都不怎麼感冒(其實是好東西),在此先不提。
總結
從個人的傾向來看,比較喜歡「掌握一種或兩種方式,然後使用最熟練,而且不出錯」的觀點,所以我只推薦第一二種方式。