python設計模式-單例模式

問題:現代化的巧克力工廠具有計算機控制的巧克力鍋爐。鍋爐作的事情就是把巧克力和牛奶融在一塊兒,而後送到下一個階段,以製成巧克力棒。下邊是一個巧克力公司鍋爐控制器的代碼,仔細觀察一下,這段代碼有什麼問題?html

class ChocolateBoiler(object):

    def __init__(self):
        self.empty = True
        self.boiled = False

    def fill(self):
        # 向鍋爐填充巧克力和牛奶混合物
        # 在鍋爐內填充原料時,鍋爐必須是空的。
        # 一旦填入原料,就要把empty 和 boiled 標誌設置好
        if self.empty:
            self.empty = False
            self.boiled = False

    def drain(self):
        # 排出煮沸的巧克力和牛奶
        # 鍋爐排出時,必須是滿的且煮沸的。
        # 排出完畢empty 設置爲 true
        if not self.empty and self.boiled:
            self.empty = True

    def boil(self):
        # 將顱內物煮沸
        # 煮混合物時,鍋爐內必須是滿的且沒有煮沸過
        # 一旦煮沸,就把 boiled 設置爲 true
        if not self.empty and not self.boiled:
            self.boiled = True

從代碼能夠看出,他們加入了多種判斷,以防止很差的事情發生。若是同時存在兩個ChocolateBoiler實例,那這麼多判斷豈不是失去做用了。那咱們改如何實現這個需求呢?這個問題的核心是,咱們要先判斷實例是否是已經存在,若是存在就再也不建立。python

_chocolate_boiler_instance = None  # 聲明實例

def chocolate_boiler():
    global _chocolate_boiler_instance  # 使用全局變量

    if _chocolate_boiler_instance is not None: # 判斷是否存在,若是存在,直接返回
        return _chocolate_boiler_instance
    else:
        # 若是不存在,建立一個新的
        _chocolate_boiler_instance = ChocolateBoiler()
        return _chocolate_boiler_instance

如今咱們須要獲取 ChocolateBoiler 實例的時候只須要調用 chocolate_boiler 方法獲取實例便可保證同時只有一個 ChocolateBoiler實例。git

這種保證 ChocolateBoiler類只有一個實例,並提供一個全局訪問點的模式,就是單例模式github

單例模式

定義

單例模式:確保一個類只有一個實例,並提供一個全局訪問點。api

  • 也就是說,咱們使用單例模式要把某個類設計成本身管理的一個單獨實例,同時也避免其餘類再自行產生實例。而且只容許經過單例類獲取單例的實例。
  • 咱們也提供對這個實例的全局訪問點:當你須要實例時,像類查詢,它會返回單個實例。

實現

python 實現單例模式有多種方案:函數

使用 metaclass

《python cookbook》提供了很是易用的 Singleton 類,只要繼承它,就會成爲單例。ui

# python 3 代碼實現
class Singleton(type):

    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            # 若是 __instance 不存在,建立新的實例
            self.__instance = super().__call__(*args, **kwargs)
            return self.__instance
        else:
            # 若是存在,直接返回
            return self.__instance


class Spam(metaclass=Singleton):

    def __init__(self):
        print('Creating Spam')

a = Spam()
b = Spam()

print(a is b)  # 這裏輸出爲 True

元類(metaclass)能夠控制類的建立過程,它主要作三件事:spa

  • 攔截類的建立
  • 修改類的定義
  • 返回修改後的類

例子中咱們構造了一個Singleton元類,並使用__call__方法使其可以模擬函數的行爲。構造類 Spam 時,將其元類設爲Singleton,那麼建立類對象 Spam 時,行爲發生以下:設計

Spam = Singleton(name,bases,class_dict),Spam 其實爲Singleton類的一個實例。code

建立 Spam 的實例時,Spam()=Singleton(name,bases,class_dict)()=Singleton(name,bases,class_dict).__call__(),這樣就將 Spam 的全部實例都指向了 Spam 的屬性 __instance上。

使用 new

咱們能夠使用 new 來控制實例的建立過程,代碼以下:

class Singleton(object):

    __instance = None

    def __new__(cls, *args, **kw):
        if not cls.__instance:
            cls.__instance = super().__new__(cls, *args, **kw)
        return cls.__instance

class Foo(Singleton):
    a = 1

one = Foo()
two = Foo()
assert one == two
assert one is two
assert id(one) == id(two)

經過 new 方法,將類的實例在建立的時候綁定到類屬性 __instance 上。若是cls.__instance 爲None,說明類還未實例化,實例化並將實例綁定到cls.__instance 之後每次實例化的時候都返回第一次實例化建立的實例。注意從Singleton派生子類的時候,不要重載__new__。

使用裝飾器

import functools

def singleton(cls):
    ''' Use class as singleton. '''
    # 首先將 __new__ 方法賦值給 __new_original__
    cls.__new_original__ = cls.__new__

    @functools.wraps(cls.__new__)
    def singleton_new(cls, *args, **kw):
        # 嘗試從 __dict__ 取 __it__
        it =  cls.__dict__.get('__it__')
        if it is not None: # 若是有值,說明實例已經建立,返回實例
            return it
        # 若是實例不存在,使用 __new_original__ 建立實例,並將實例賦值給 __it__
        cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
        it.__init_original__(*args, **kw)
        return it
    # class 將原有__new__ 方法用 singleton_new 替換
    cls.__new__ = singleton_new
    cls.__init_original__ = cls.__init__
    cls.__init__ = object.__init__

    return cls

#
# 使用示例
#
@singleton
class Foo:
    def __new__(cls):
        cls.x = 10
        return object.__new__(cls)

    def __init__(self):
        assert self.x == 10
        self.x = 15


assert Foo().x == 15
Foo().x = 20
assert Foo().x == 20

這種方法的內部實現和使用 __new__ 相似:

  • 首先,將 new 方法賦值給 new_original__,原有 new 方法用 singleton_new 替換,定義 init_original 並將 cls.__init 賦值給 init_original
  • 在 singleton_new 方法內部,嘗試從 dict 取 __it__(實例)
  • 若是實例不存在,使用 new_original 建立實例,並將實例賦值給 __it__,而後返回實例

最簡單的方式

將名字singleton綁定到實例上,singleton就是它本身類的惟一對象了。

class singleton(object):
    pass
singleton = singleton()

https://github.com/gusibi/Metis/blob/master/apis/v1/schemas.py#L107 使用的就是這種方式,用來獲取全局的 request

Python 的模塊就是自然的單例模式,由於模塊在第一次導入時,會生成 .pyc 文件,當第二次導入時,就會直接加載 .pyc 文件,而不會再次執行模塊代碼。所以,咱們只需把相關的函數和數據定義在一個模塊中,就能夠得到一個單例對象了。

參考連接


最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注 請我喝芬達
相關文章
相關標籤/搜索