【Python設計模式】02 單例模式
1. python實現經典的單例模式
python通過覆蓋__new__()方法來控制物件的建立。
if not hasattr(cls, ‘instance’):方法hasattr用於檢視物件cls是否具有屬性instance, 該屬性的作用是檢查該類是否已經生成了一個物件。
class singleton(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(singleton, cls).__new__(cls)
return cls.instance
s = Singleton()
print("Object created", s)
s1 = Singleton()
print("Object created", s1)
執行結果:
Object created <main.Singleton object at 0x7eff5af722e8>
Object created <main.Singleton object at 0x7eff5af722e8>
2. 單例模式中的懶漢式例項化
懶漢式例項化能夠確保在實際需要時才建立物件,有效節約資源。
class Singleton:
__instance = None
def __init__(self):
if not Singleton.__instance:
print("__init__ method called..")
else:
print("Instance already created:", \
self.getInstance())
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = Singleton()
return cls.__instance
s1 = Singleton( )
print("Object created,", Singleton.getInstance())
s2 = Singleton()
print("Object created,", Singleton.getInstance())
執行結果:
init method called…
init method called…
Object created, <main.Singleton object at 0x7eff5a74c978>
Instance already created: <main.Singleton object at 0x7eff5a74c978>
Object created, <main.Singleton object at 0x7eff5a74c978>
3. Monostate 單例模式
通常程式設計師需要的是讓例項共享相同的狀態
開發人員應該關注狀態和行為, 而不是同一性
由於Monostate單例模式是基於所有物件共享相同狀態,因此也被稱為單態模式
與單例模式不同的地方: 建立兩個例項b, b1, 會生成兩個不同的物件,而單例模式只能生成一個物件
然而物件的狀態,即b.__dict__和b1.__dict__卻是相同的
class Borg:
__shared_state = {"1": "2"}
def __init__(self):
self.x = 1
self.__dict__ = self.__shared_state
pass
b = Borg()
b1 = Borg()
b.x = 4
print("Borg Object 'b' : ", b)
print("Borg Object 'b1': ", b1)
print("Borg state 'b' : ", b.__dict__)
print("Borg state 'b1': ", b1.__dict__)
執行結果:
Borg Object ‘b’ : <main.Borg object at 0x7eff5a74c908>
Borg Object ‘b1’: <main.Borg object at 0x7eff5a74c400>
Borg state ‘b’ : {‘1’: ‘2’, ‘x’: 4}
Borg state ‘b1’: {‘1’: ‘2’, ‘x’: 4}
除此之外, 我們還可以通過修改__new__方法本身來實現 Borg 模式
class Borg(object):
_shared_state = {"a":"as"}
def __new__(cls, *args, **kwargs):
obj = super(Borg, cls).__new__(cls, *args, **kwargs)
obj.__dict__ = cls._shared_state()
return obj
4. 單例和元類
類的定義由它的元類決定,所以當我們用類 A 建立一個類時,Python 通過A=Type(name, bases, dict)建立它
- name : 這是類的名稱
- base : 這是基類
- dict : 這是屬性變數
# 假設一個類int 有預定義的元類MyInt
class MyInt(type):
def __call__(cls, *args, **kwds):
print("****** Here's My int ******", args)
print("Now do whatever you want with these objects...")
return type.__call__(cls, *args, **kwds)
class int(metaclass=MyInt):
def __init__(self, x, y):
self.x = x
self.y = y
i = int(4, 5)
執行結果:
****** Here’s My int ****** (4, 5)
Now do whatever you want with these objects…
對於已經存在的類來說,當需要建立物件時,將呼叫Python的__call__()方法
在這段程式碼中, 當我們使用int(4, 5)例項化類int時,元類MyInt的__call__()方法
將被呼叫,這意味著元類控制著物件是的例項化
由於元類對類的建立和物件例項化有更多的控制權,所以它可以用於建立單例
注意:為了控制類的建立和初始化,元類將覆蓋__new__和__init__方法
以下示例程式碼是基於元類的單例實現
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, \
cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(metaclass=MetaSingleton):
pass
logger1 = Logger()
logger2 = Logger()
print(logger1)
print(logger2)
執行結果:
<main.Logger object at 0x7eff5af9ec18>
<main.Logger object at 0x7eff5af9ec18>
5. 單例模式應用I
以需要對資料庫進行多種讀取和寫入操作的雲服務為例
完整的雲服務被分解為多個服務,每個服務執行不同的資料庫操作
針對UI(Web應用程式)上的操作將導致呼叫API, 最終產生相應的DB操作
很明顯,跨不同服務的共享資源是資料庫本身,因次,設計雲服務需要注意:
- 資料庫中的操作的一致性,即一個操作不應與其他操作發生衝突
- 優化資料庫的各種操作,以提高記憶體和CPU的利用率
import sqlite3
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(MetaSingleton, \
cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=MetaSingleton):
connection = None
def connect(self):
if self.connection is None:
self.connection = sqlite3.connect("db.sqlite3")
self.cursorobj = self.connection.cursor()
return self.cursorobj
db1 = Database().connect()
db2 = Database().connect()
print("Database Objects DB1", db1)
print("Database Objects DB2", db2)
執行結果:
Database Objects DB1 <sqlite3.Cursor object at 0x7eff5a74b5e0>
Database Objects DB2 <sqlite3.Cursor object at 0x7eff5a74b5e0>
我們發現:
- 元類MetaSingleton,可以通過python的方法__call__,建立單例
- 資料庫類由元類裝飾後,其行為就會表現為單例,因此當資料庫類被例項化時,它只建立一個物件
- 因為只有一個物件,所以對資料庫的呼叫是同步的,節約資源,避免消耗過多的記憶體和CPU
但是,加入我們要開發的不是單個Web應用程式,而是叢集化,即多個Web應用共享一個數據庫
這樣單例在這種情況下就不好使了,因為每新增一個Web應用程式,就要新建一個單例,新增一個新的物件來查詢資料庫,這導致資料庫無法同步,並且要消耗大量的資源
這種情況下,資料庫連線池比實現單例要好得多
6. 單例模式應用II
為基礎設施提供執行狀況監控服務(就像Nagios一樣)
我們建立HealthCheck類,它作為單例實現。我們還要維護一個被監控的伺服器列表。
當一個伺服器從這個列表中刪除時,監控軟體應該察覺到這一情況,並從被監控的伺服器列表中將其刪除
class HealthCheck:
_instance = None
def __new__(cls, *args, **kwargs):
if not HealthCheck._instance:
HealthCheck._instance = super(HealthCheck, \
cls).__new__(cls, *args, **kwargs)
return HealthCheck._instance
def __init__(self):
self._servers = []
def addServer(self):
self._servers.append("Server 1")
self._servers.append("Server 2")
self._servers.append("Server 3")
self._servers.append("Server 4")
def changeServer(self):
self._servers.pop()
self._servers.append("Server 5")
hc1 = HealthCheck()
hc2 = HealthCheck()
hc1.addServer()
print("Schedules health check for servers (1)..")
for i in range(4):
print("Checking ", hc1._servers[i])
hc2.changeServer()
print("Schedules health check for servers (2)..")
for i in range(4):
print("Checking ", hc2._servers[i])
執行結果:
Schedules health check for servers (1)…
Checking Server 1
Checking Server 2
Checking Server 3
Checking Server 4
Schedules health check for servers (2)…
Checking Server 1
Checking Server 2
Checking Server 3
Checking Server 5
changeServer()方法會刪除最後一個伺服器,並想計劃進行執行狀況檢查的基礎設施中新增一個新的伺服器Server 5。因此,當執行狀況檢查進行第二次迭代時,它會使用修改後的伺服器列表。
7. 單例模式的缺點
由於單例具有全域性訪問許可權,因此可能會出現以下問題:
- 全域性變數可能在某處已經被誤改,但是開發人員仍然認為它們沒有發生變化,而該變數還在應用程式中的其他位置被使用
- 可能會對同一個物件建立多個引用。由於單例只建立一個物件,因此這種情況下會對同一個物件建立多個引用
- 所有依賴於全域性變數的類都會由於一個類的改變而緊密耦合為全域性資料,從而可能在無意中影響另一個類
8. 單例模式小結
a. 在許多應用程式中,我們只需建立一個物件,如執行緒池、快取、對話方塊、登錄檔設定等。如果為每個應用程式建立多個例項,則會導致資源過度使用。單例模式在這種情況下工作得很好。
b. 單例是一種經過時間考驗的成熟方法,能夠在不帶來太多缺陷的情況下提供全域性訪問點。
c. 經典的單例模式:允許進行多次例項化,但最終只返回同一個物件
d. Borg或Monostate模式:這是單例模式的變體。Borg允許建立相同狀態的多個物件,這與GoF描述的單例模式有所不同
e. Web應用:單例模式可以用於在多個服務間實現一致的資料操作
f. 最後,我們研究了單例可能出現的錯誤,以及開發人員需要避免的情況