python設計模式之單例模式


無論是在python代碼中,還是面試中單例設計模式都是經常被問到和使用的,例如面試中會讓你用代碼實現單例模式分幾種不同的方式,或者問你在平常工作中哪些地方有用到單例設計模式,然后深入探討。

在本文中我將針對這兩個問題來回答和用python代碼來編寫我們的單例模式。

首先,我們要了解什么是單例模式--官方解釋是:確保一個類只有一個實例(也就是類的對象),並且提供一個全局的訪問點(外部通過這個訪問點來訪問該類的唯一實例)。通俗的說就是,無論類實例化多少次,都只有一個相同的對象產生,並且可以通過一個具柄去訪問這個這個唯一實例。

 

其次,我們平時代碼中碰到是用單例模式設計的代碼有哪些呢?

  1. 應用框架的配置文件config(如flask,django框架的配置文件)
  2. 線程池,數據庫連接池的創建
  3. 應用程序的日志應用

 

這些應用都有一個共性那就是:他們都是全局共享的對象不會發生變化,不希望重復創建,這樣有利於節省空間和減少性能消耗。

 

python實現單例模式的幾種方式分別有:

  1. 通過類方法實現
  2. 通過裝飾器實現
  3. 通過__new__方法實現
  4. 通過元類實現

 

一、類方法實現

class SingleType: _instance = None @classmethod def instance(cls): if not cls._instance: cls._instance = SingleType() return cls._instance for i in range(100): print(SingleType.instance())

看一下打印結果:

 

 返回的都是同一個對象,實現了我們說的,無論調用多少次,都只返回同一個實例,但是這在多線程的環境中,可能會存在返回多個實例,我們實驗一下。

import threading
import time


class SingleType:
    _instance = None
    def __init__(self):
        time.sleep(1)
        super().__init__()

    @classmethod
    def instance(cls):
        if not cls._instance:
            cls._instance = SingleType()
        return cls._instance

def task():
    obj = SingleType.instance()
    print(obj)

for i in range(10):
    t = threading.Thread(target=task).start()
    print(SingleType.instance())

 這是多線程環境測試的代碼結果如下:

 

 可以看到在多線程的環境中,我們產生了兩個2個對象,這就違背了我們的單例原則了,那怎么解決它呢,答案是加鎖。

import threading import time class SingleType: _instance = None _lock = threading.Lock() def __init__(self): time.sleep(1) super().__init__() @classmethod def instance(cls): if not cls._instance: with SingleType._lock: if not cls._instance: cls._instance = SingleType() return cls._instance def task(): obj = SingleType.instance() print(obj) for i in range(10): t = threading.Thread(target=task).start()

加鎖后我們看運行結果:

 

 這樣就成功解決在多線程環境下單例的問題了, 至於問什么在最外層還要加一個if判斷,是為了提高程序的性能。

 

二、裝飾器模式實現

這種實現方式在我上一片將裝飾器一文中是有提及的,在這我們再實現一次

def decorator(cls): def wrap(*args, **kwargs): if not cls._instance: cls._instance = cls() return cls._instance return wrap @decorator class SingleType: _instance = None def __init__(self): super().__init__() for i in range(10): obj = SingleType() print(obj)

當然這里也會出現多線程創建多個實例的問題,可以參照上一個例子自行去實現多線程的版本。

 

三、使用__new__方法實現

在說__new__方法實現之前,我們需要有個前提知識,我們對象在實例話時會調用__init__方法,但是在調用__init__方法之前,類會先調用__new__方法,__new__方法中決定實例化怎樣的對象(可以是調用object的__new__方法正常接着實例話,可以生成其他類的實例等)具體知識可以自行去補充,本文主要講單例模式。

class SingleType: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance def __init__(self): super().__init__() for i in range(10): obj = SingleType() print(obj)

查看運行結果:

 

 

四、使用元類實現單例模式

前提知識: 實例化對象時方法調用順序是: type類的__cal__方法,其中會調用__new__方法和__init__方法

import threading class SingleType(type): lock = threading.Lock() def __call__(cls, *args, **kwargs): if not cls._instance: with SingleType.lock: if not cls._instance: cls._instance = super().__call__(*args, **kwargs) # 這個地方也可以替換成 obj = cls.__new__(cls) --> cls.__init__(onj) --> cls._instance=obj return cls._instance class Test(metaclass=SingleType): _instance = None for i in range(10): obj = Test() print(obj)

在代碼注釋部分的作用相當於自己實現type.__call__的功能。

 

至此python實現單例設計模式所有的實現方式已經講完,既然說的是設計模式就要把設計模式的八大准則貼出來,時常警醒自己。

1、依賴倒置原則(DIP):高層模塊不依賴於低層模塊,而是都依賴於抽象;抽象不依賴於實現細節,實現細節應該依賴於抽象。
 
2、開放封閉原則(OCP):對擴展開放,對依賴關閉;類的模塊是可擴展的,但是不可修改。
 
3、單一職責原則(SRP):一個類僅有一個能引起他的變化,變化的方向隱含着類的責任。
 
4、LisKov替換原則:派這類能夠替換他的基類,繼承表達了類的抽象。
 
5、接口隔離原則(ISP):不應該強迫客戶程序依賴他們不用的方法,接口應該小而強大。
 
6、優先使用對象組合,而不是類繼承:類的繼承破壞了封裝性,子類父類耦合度高,類繼承通常稱為“白箱復用和“,對象組合稱為”黑箱復用”。並且對象組合只需要有良好的定義的接口,且耦合度低。
 
7、封裝變化點:使用封裝來創建對象之間的分界層,讓設計者可以在分界層的一側修改,而不會對另一側產生不良影響。
 
8、針對接口編程,而不是針對實現編程:減少系統中個部分的依賴關系,從而實現”高內聚,松耦合“的類型設計方案;不將變量類型聲明為某個特定的具體類,而是聲明為某個接口。 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM