1. 程式人生 > >一文詳解“單例模式”及其python語言的實現

一文詳解“單例模式”及其python語言的實現

一、什麼是“單例模式”——一個例項

單例模式(Singleton Pattern)是一種常用的軟體設計模式,該模式的主要目的是確保某一個類只有一個例項存在。當你希望在整個系統中,某個類只能出現一個例項時,單例物件就能派上用場。

單例模式涉及到一個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件。

注意:

  • 1、單例類只能有一個例項。
  • 2、單例類必須自己建立自己的唯一例項。
  • 3、單例類必須給所有其他物件提供這一例項。

關鍵實現思想

(1)保證建構函式是私有的(比如java、C++、C#)語言,像python這種沒辦法構造私有函式的怎麼辦呢?後面會講到它靈活的實現。

(2)第一次建立類的物件的時候判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。那麼後續再次建立該類的例項的時候,因為已經建立過一次了,就不能再建立新的例項了,否則就不是單例啦,直接返回前面返回的例項即可。

二、為什麼要實現單例模式呢?

常見的單例模式:

(1)比如,某個伺服器程式的配置資訊存放在一個檔案中,客戶端通過一個 AppConfig 的類來讀取配置檔案的資訊。如果在程式執行期間,有很多地方都需要使用配置檔案的內容,也就是說,很多地方都需要建立 AppConfig 物件的例項,這就導致系統中存在多個 AppConfig 的例項物件,而這樣會嚴重浪費記憶體資源,尤其是在配置檔案內容很多的情況下。事實上,類似 AppConfig 這樣的類,我們希望在程式執行期間只存在一個例項物件。

(2)當有同步需要的時候,可以通過一個例項來進行同步控制,比如對某個共享檔案(如日誌檔案)的控制,對計數器的同步控制等,這種情況下由於只有一個例項,所以不用擔心同步問題。

(3)Python的logger就是一個單例模式,用以日誌記錄;Windows的資源管理器是一個單例模式;執行緒池,資料庫連線池等資源池一般也用單例模式;網站計數器等等,這些都是單例模式。

需要使用單例模式的情景:——這個很重要哦

當每個例項都會佔用資源,而且例項初始化會影響效能,這個時候就可以考慮使用單例模式,它給我們帶來的好處是隻有一個例項佔用資源,並且只需初始化一次;歸納為以下幾條:

1、要求生產唯一序列號。

2、WEB 中的計數器,不用每次重新整理都在資料庫里加一次,用單例先快取起來。

3、建立的一個物件需要消耗的資源過多,比如 I/O 與資料庫的連線等。

優缺點:

優點: 1、在記憶體裡只有一個例項,減少了記憶體的開銷,尤其是頻繁的建立和銷燬例項(比如管理學院首頁頁面快取)。 2、避免對資源的多重佔用(比如寫檔案操作)。

缺點:沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

三、單例模式的python實現

不同的程式語言對於單例模式的實現有所不同,因為每一種語言的語法有所差異,但是所遵循的核心原理以及所要達到的最終目的是一樣的,不一樣的是,往目的地所走的路不一樣而已。python實現單例模式的方法有很多,本文著重講解三種。

1、首先看一下普通的類——即非單例類

class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age



s1=Student('張三',23)
s2=Student('張三',23)

print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep='   ')

執行結果如下:

False
False
2215920481224   2215920481112

從上面可以看出,在非單例模式下,兩個例項是完全不一樣的。

2、使用“裝飾器”來實現單例模式

def Singleton(cls):   #這是一個函式,目的是要實現一個“裝飾器”,而且是對型別的裝飾器
    '''
    cls:表示一個類名,即所要設計的單例類名稱,
        因為python一切皆物件,故而類名同樣可以作為引數傳遞
    '''
    instance = {}

    def singleton(*args, **kargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kargs)   #如果沒有cls這個類,則建立,並且將這個cls所建立的例項,儲存在一個字典中
        return instance[cls]

    return singleton


@Singleton
class Student(object):
    def __init__(self, name,age):
        self.name=name
        self.age=age

s1 = Student('張三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep='   ')

執行結果為:

True
True
2150787003840   2150787003840

從上面可以看出,雖然建立的兩個物件s1 s2看起來是不一樣的,但是實際上都是一樣的,這就是“單例模式”

3、通過__new__函式去實現

class Student(object):
    instance = None
    def __new__(cls, name,age):
        if not cls.instance:
            cls.instance = super(Student, cls).__new__(cls)  
        return cls.instance  

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


s1 = Student('張三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep='   ')

執行結果如下:

True
True
1718008231696   1718008231696

從上面可見,依然是實現了單例模式。

總結:上面這兩種方法,都是python實現單例模式最常用的方法,通過比較發現,這兩種方法都有一個共同點,那就是都是通過一個內部變數instance作為橋樑,如果它不是空,就建立一個類的實力繫結到它上面,後續它不為空了,會一直都是這個繫結的物件,這就實現了單例模式。

4、使用一個單獨的模組作為單例模式

因為,Python 的模組就是天然的單例模式,因為模組在第一次匯入時,會生成 .pyc 檔案,當第二次匯入時,就會直接載入 .pyc 檔案,而不會再次執行模組程式碼。因此,我們只需把相關的函式和資料定義在一個模組中,就可以獲得一個單例物件了。如果我們真的想要一個單例類,可以考慮這樣做:

在一個模組中定義一個普通的類,如在Singleton_Pattern.py模組中定義如下程式碼

class Student:
    def __init__(self,name,age):
        self.name=name
        self.age=age

student=Student('張三',23)

這裡的student就是一個單例。當我們在另外一個模組中匯入student這個物件時,因為它只被匯入了一次,所以總是同一個例項。

當然python的單例模式非常的靈活多變,這也是因為Python語言的靈活性所決定的,我們還可以使用元類建立單例模式,甚至還有很多其他的方式都可以創家門單例模式,只要記住單例模式的本質即可。推薦使用前面的兩種方法,即裝飾器方法和__new__
方法
。因為原理清晰簡單,很容易理解。