設計模式詳解及Python實現

嗨,阿良發表於2020-08-15

設計模式及Python實現

設計模式是什麼?

Christopher Alexander:“每一個模式描述了一個在我們周圍不斷重複發生的問題,以及該問題的解決方案的核心*。這樣你就能一次又一次地使用該方案而不必做重複勞動。”

設計模式是經過總結、優化的,對我們經常會碰到的一些程式設計問題的可重用解決方案。一個設計模式並不像一個類或一個庫那樣能夠直接作用於我們的程式碼。反之,設計模式更為高階,它是一種必須在特定情形下實現的一種方法模板。設計模式不會繫結具體的程式語言。一個好的設計模式應該能夠用大部分程式語言實現(如果做不到全部的話,具體取決於語言特性)。最為重要的是,設計模式也是一把雙刃劍,如果設計模式被用在不恰當的情形下將會造成災難,進而帶來無窮的麻煩。然而如果設計模式在正確的時間被用在正確地地方,它將是你的救星。

起初,你會認為“模式”就是為了解決一類特定問題而特別想出來的明智之舉。說的沒錯,看起來的確是通過很多人一起工作,從不同的角度看待問題進而形成的一個最通用、最靈活的解決方案。也許這些問題你曾經見過或是曾經解決過,但是你的解決方案很可能沒有模式這麼完備。

雖然被稱為“設計模式”,但是它們同“設計“領域並非緊密聯絡。設計模式同傳統意義上的分析、設計與實現不同,事實上設計模式將一個完整的理念根植於程式中,所以它可能出現在分析階段或是更高層的設計階段。很有趣的是因為設計模式的具體體現是程式程式碼,因此可能會讓你認為它不會在具體實現階段之前出現(事實上在進入具體實現階段之前你都沒有意識到正在使用具體的設計模式)。

可以通過程式設計的基本概念來理解模式:增加一個抽象層。抽象一個事物就是隔離任何具體細節,這麼做的目的是為了將那些不變的核心部分從其他細節中分離出來。當你發現你程式中的某些部分經常因為某些原因改動,而你不想讓這些改動的部分引發其他部分的改動,這時候你就需要思考那些不會變動的設計方法了。這麼做不僅會使程式碼可維護性更高,而且會讓程式碼更易於理解,從而降低開發成本。

三種最基本的設計模式:

  1. 建立模式:提供例項化的方法,為適合的狀況提供相應的物件建立方法。
  2. 結構化模式:通常用來處理實體之間的關係,使得這些實體能夠更好地協同工作。
  3. 行為模式:用於在不同的實體間進行通訊,為實體之間的通訊提供更容易,更靈活的通訊方法。

設計模式六大原則

  • 開閉原則:一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。即軟體實體應儘量在不修改原有程式碼的情況下進行擴充套件。
  • 里氏(Liskov)替換原則:所有引用基類(父類)的地方必須能透明地使用其子類的物件
  • 依賴倒置原則:高層模組不應該依賴低層模組,二者都應該依賴其抽象;抽象不應該依賴細節;細節應該依賴抽象。換言之,要針對介面程式設計,而不是針對實現程式設計
  • 介面隔離原則:使用多個專門的介面,而不使用單一的總介面,即客戶端不應該依賴那些它不需要的介面。
  • 迪米特法則:一個軟體實體應當儘可能少地與其他實體發生相互作用
  • 單一職責原則:不要存在多於一個導致類變更的原因。通俗的說,即一個類只負責一項職責

介面

介面:一種特殊的類,宣告了若干方法,要求繼承該介面的類必須實現這些方法
作用:限制繼承該介面的類的方法的名稱呼叫方式;隱藏了類的內部實現。

介面就是一種抽象的基類(父類),限制繼承它的類必須實現介面中定義的某些方法。

Python中使用ABCMetaabstractmethod的抽象類、抽象方法來實現介面的功能。介面類定義方法,不具體實現,限制子類必須有該方法。在介面子類中實現具體的功能。

# 通過抽象類和抽象方法,做抽象用
from abc import ABCMeta
from abc import abstractmethod  # 匯入抽象方法


class Father(metaclass=ABCMeta):  # 建立抽象類
    
    @abstractmethod
    def f1(self):
        pass

    @abstractmethod
    def f2(self):
        pass

class F1(Father):
    def f1(self):
        pass

    def f2(self):
        pass

    def f3(self):
        pass

obj = F1()

報錯定義介面

class Interface:
    def method(self, arg):
        raise NotImplementedError

一、建立型模式

建立模式可以在直接使用類的構造方法例項化物件並不方便時,為開發者提供更好的例項化物件的方式。

1. 簡單工廠模式

內容:不直接向客戶端暴露物件建立的實現細節,而是通過一個工廠類來負責建立產品類的例項

角色

  • 工廠角色(Creator)
  • 抽象產品角色(Product)
  • 具體產品角色(Concrete Product)

優點

  • 隱藏了物件建立的實現細節
  • 客戶端不需要修改程式碼

缺點

  • 違反了單一職責原則,一個工廠類中實現了多種建立邏輯
  • 當新增新產品時,需要修改工廠類程式碼,違反了開閉原則

PaymentFactory簡單工廠

from abc import abstractmethod, ABCMeta


class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self, money):
        pass


class Alipay(Payment):
    def __init__(self, enable_yuebao=False):
        self.enable_yuebao = enable_yuebao

    def pay(self, money):
        if self.enable_yuebao:
            print("餘額寶支付%s元" % money)
        else:
            print("支付寶支付%s元" % money)


class ApplePay(Payment):
    def pay(self, money):
        print("蘋果支付%s元" % money)


class PaymentFactory:
    def create_payment(self, method):
        if method == "alipay":
            return Alipay()
        elif method == 'yuebao':
            return Alipay(enable_yuebao=True)
        elif method == "applepay":
            return ApplePay()
        else:
            raise NameError(method)


f = PaymentFactory()
p = f.create_payment("yuebao")
p.pay(100)

2. 工廠方法模式(Factory Method)

內容:定義一個用於建立物件的介面(工廠介面),讓子類決定例項化哪一個產品類。

角色

  • 抽象工廠角色(Creator)
  • 具體工廠角色(Concrete Creator)
  • 抽象產品角色(Product)
  • 具體產品角色(Concrete Product)

工廠方法模式相比簡單工廠模式將每個具體產品都對應了一個具體工廠。

適用場景

  • 需要生產多種、大量複雜物件的時候。
  • 需要降低耦合度的時候。
  • 當系統中的產品種類需要經常擴充套件的時候。

優點

  • 每個具體產品都對應一個具體工廠類,不需要修改工廠類程式碼
  • 隱藏了物件建立的實現細節

缺點

  • 每增加一個具體產品類,就必須增加一個相應的具體工廠類

img

工廠方法

from abc import abstractmethod, ABCMeta


class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self, money):
        pass


class Alipay(Payment):
    def pay(self, money):
        print("支付寶支付%s元" % money)


class ApplePay(Payment):
    def pay(self, money):
        print("蘋果支付%s元" % money)


class PaymentFactory(metaclass=ABCMeta):
    @abstractmethod
    def create_payment(self):
        pass


class AlipayFactory(PaymentFactory):
    def create_payment(self):
        return Alipay()


class ApplePayFactory(PaymentFactory):
    def create_payment(self):
        return ApplePay()


af = AlipayFactory()
ali = af.create_payment()
ali.pay(120)

3. 抽象工廠方法(Abstract Factory)

內容:定義一個工廠類介面,讓工廠子類來建立一系列相關或相互依賴的物件。
例:生產一部手機,需要手機殼、CPU、作業系統三類物件進行組裝,其中每類物件都有不同的種類。對每個具體工廠,分別生產一部手機所需要的三個物件中對應的一個。
角色

  • 抽象工廠角色(Creator)
  • 具體工廠角色(Concrete Creator)
  • 抽象產品角色(Product)
  • 具體產品角色(Concrete Product)
  • 客戶端(Client)

相比工廠方法模式,抽象工廠模式中的每個具體工廠都生產一套產品。
適用場景

  • 系統要獨立於產品的建立與組合時
  • 強調一系列相關的產品物件的設計以便進行聯合使用時
  • 提供一個產品類庫,想隱藏產品的具體實現時

優點

  • 將客戶端與類的具體實現相分離
  • 每個工廠建立了一個完整的產品系列,使得易於交換產品系列
  • 有利於產品的一致性(即產品之間的約束關係)

缺點

  • 難以支援新種類的(抽象)產品

img

抽象工廠

from abc import abstractmethod, ABCMeta


# ------抽象產品------
class PhoneShell(metaclass=ABCMeta):
    @abstractmethod
    def show_shell(self):
        pass


class CPU(metaclass=ABCMeta):
    @abstractmethod
    def show_cpu(self):
        pass


class OS(metaclass=ABCMeta):
    @abstractmethod
    def show_os(self):
        pass


# ------抽象工廠------

class PhoneFactory(metaclass=ABCMeta):
    @abstractmethod
    def make_shell(self):
        pass

    @abstractmethod
    def make_cpu(self):
        pass

    @abstractmethod
    def make_os(self):
        pass


# ------具體產品------

class SmallShell(PhoneShell):
    def show_shell(self):
        print("普通手機小手機殼")


class BigShell(PhoneShell):
    def show_shell(self):
        print("普通手機大手機殼")


class AppleShell(PhoneShell):
    def show_shell(self):
        print("蘋果手機殼")


class SnapDragonCPU(CPU):
    def show_cpu(self):
        print("驍龍CPU")


class MediaTekCPU(CPU):
    def show_cpu(self):
        print("聯發科CPU")


class AppleCPU(CPU):
    def show_cpu(self):
        print("蘋果CPU")


class Android(OS):
    def show_os(self):
        print("Android系統")


class IOS(OS):
    def show_os(self):
        print("iOS系統")


# ------具體工廠------

class MiFactory(PhoneFactory):
    def make_cpu(self):
        return SnapDragonCPU()

    def make_os(self):
        return Android()

    def make_shell(self):
        return BigShell()


class HuaweiFactory(PhoneFactory):
    def make_cpu(self):
        return MediaTekCPU()

    def make_os(self):
        return Android()

    def make_shell(self):
        return SmallShell()


class IPhoneFactory(PhoneFactory):
    def make_cpu(self):
        return AppleCPU()

    def make_os(self):
        return IOS()

    def make_shell(self):
        return AppleShell()


# ------客戶端------

class Phone:
    def __init__(self, cpu, os, shell):
        self.cpu = cpu
        self.os = os
        self.shell = shell

    def show_info(self):
        print("手機資訊:")
        self.cpu.show_cpu()
        self.os.show_os()
        self.shell.show_shell()


def make_phone(factory):
    cpu = factory.make_cpu()
    os = factory.make_os()
    shell = factory.make_shell()
    return Phone(cpu, os, shell)


p1 = make_phone(HuaweiFactory())
p1.show_info()

4. 建造者模式(Builder)

內容:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
角色

  • 抽象建造者(Builder)
  • 具體建造者(Concrete Builder)
  • 指揮者(Director)
  • 產品(Product)

建造者模式與抽象工廠模式相似,也用來建立複雜物件。主要區別是建造者模式著重一步步構造一個複雜物件,而抽象工廠模式著重於多個系列的產品物件。

適用場景

  • 當建立複雜物件的演算法(Director)應該獨立於該物件的組成部分以及它們的裝配方式(Builder)時
  • 當構造過程允許被構造的物件有不同的表示時(不同Builder)。

優點

  • 隱藏了一個產品的內部結構和裝配過程
  • 將構造程式碼與表示程式碼分開
  • 可以對構造過程進行更精細的控制

img

建造者模式

from abc import abstractmethod, ABCMeta

# ------產品------

class Player:
    def __init__(self, face=None, body=None, arm=None, leg=None):
        self.face = face
        self.arm = arm
        self.leg = leg
        self.body = body

    def __str__(self):
        return "%s, %s, %s, %s" % (self.face, self.arm, self.body, self.leg)


# ------建造者------

class PlayerBuilder(metaclass=ABCMeta):
    @abstractmethod
    def build_face(self):
        pass

    @abstractmethod
    def build_arm(self):
        pass

    @abstractmethod
    def build_leg(self):
        pass

    @abstractmethod
    def build_body(self):
        pass

    @abstractmethod
    def get_player(self):
        pass


class BeautifulWomanBuilder(PlayerBuilder):
    def __init__(self):
        self.player = Player()

    def build_face(self):
        self.player.face = "漂亮臉蛋"

    def build_arm(self):
        self.player.arm = "細胳膊"

    def build_body(self):
        self.player.body = "細腰"

    def build_leg(self):
        self.player.leg = "長腿"

    def get_player(self):
        return self.player


class PlayerDirector:
    def build_player(self, builder):
        builder.build_body()
        builder.build_arm()
        builder.build_leg()
        builder.build_face()
        return builder.get_player()


director = PlayerDirector()
builder = BeautifulWomanBuilder()
p = director.build_player(builder)
print(p)

5. 單例模式(Singleton)

內容:保證一個類只有一個例項,並提供一個訪問它的全域性訪問點。
角色

  • 單例(Singleton)

適用場景

  • 當類只能有一個例項而且客戶可以從一個眾所周知的訪問點訪問它時

  • 單例模式也可以用於對應用中共享的資源或功能的訪問進行限制.

    比如資料庫的連線物件或者記錄日誌的物件就可以用單例模式建立, 全域性使用一份, 不僅避免了每次重新例項化物件浪費的資源, 也便於應用整體的管理.

優點

  • 對唯一例項的受控訪問
  • 單例相當於全域性變數,但防止了名稱空間被汙染。

與單例模式功能相似的概念:全域性變數、靜態變數(方法)

img

常見誤區與注意事項

首先, 為了保證一個類的物件在應用中只有一份, 我們除了要在例項化的過程中進行限制以外, 還需要注意禁止對這類物件的拷貝.

另外, Python中使用單例模式的一大誤區就是依靠重寫__new__方法的方式實現單例模式. 這種寫法可能在一般使用情況下沒有問題, 但當需要繼承擴充套件使用單例模式的類時就會出現一些無法預期的效果. 雖然單例類一般不應該被繼承擴充套件, 尤其不該有多層繼承, 但是瞭解可能出現的錯誤以及如何避免可以讓程式碼更加的健壯.

示例程式碼

一、不完美寫法(重寫 __new__ 方法)
class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance


class Child(Singleton):
    pass


def identity_test():
    s1 = Singleton()
    s2 = Singleton()
    print(s1 is s2)  # True
    exit(0)


def create_parent_first():
    s1 = Singleton()
    c1 = Child()
    print(c1 is s1)  # True
    exit(0)


def create_child_first():
    c1 = Child()
    s1 = Singleton()
    print(c1 is s1)  # False
    exit(0)

從以上示例程式碼的執行結果中可以看出重寫 __new__ 方法實現單例模式的類被繼承時的一些問題:

  • 當系統中同時需要父類和子類, 假如先例項化父類, 後例項化子類, 它們其實指向的是同一個物件, 這顯然是不合理的.
  • 而當先例項化子類, 後例項化父類時, 它們指向的卻又不是同一個物件. 在複雜的系統中, 父類和子類誰先被例項化可能是不確定的, 這樣就帶來了未知的隱患, 而這種問題導致的bug也非常的難排查.

注意:在互動直譯器中連續執行create_parent_first()create_child_first(), 在執行第二個函式時由於記憶體中已經有了例項化的物件, 會顯示錯誤的結果.

二、完美寫法

1、單例模式的正確寫法應該是使用元類. 首先定義一個單例元類, 然後在宣告需要使用單例模式的類時指定單例元類作為元類.

class SingletonType(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
    
    
class DatabaseConnector(metaclass=SingletonType):
    pass


class MySQLConnector(DatabaseConnector):
    pass


def identity_test():
    d1 = DatabaseConnector()
    d2 = DatabaseConnector()
    m1 = MySQLConnector()
    m2 = MySQLConnector()
    print(d1 is d2) # True
    print(m1 is m2) # True
    exit(0)
    
    
def create_parent_first():
    d = DatabaseConnector()
    m = MySQLConnector()
    print(d is m) # False
    exit(0)
    
    
def create_child_first():
    m = MySQLConnector()
    d = DatabaseConnector()
    print(d is m) # False
    exit(0)

從執行結果中可以看出, 在錯誤寫法中出現的問題在使用元類實現單例時都不存在, 子類與父類永遠指向不同的單例物件, 不受例項化順序的影響.

2、裝飾器方法

def singleton(cls, *args, **kw):
    instances = {}

    def get_instance():
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]

    return get_instance


@singleton
class MyClass2:
    a = 1


one = MyClass2()
two = MyClass2()

print(id(one))  # 31495472
print(id(two))  # 31495472
print(one == two)
print(one is two)

Python開發者對於單例模式的不同看法

單例模式在Python中是比較受爭議的一種設計模式. 許多開發者認為使用單例模式來保證程式中某些東西的唯一性的寫法過於臃腫, 在Python中可以藉助語言的一些特性用更自然, 更符合Python風格的寫法實現類似的功能.

同時, 在一些場景中應該謹慎使用或者避免單例模式, 比如分散式計算, 自動化測試等. 這些場景中使用單例模式可能反而會產生負面效果.

要不要用單例模式, 怎麼用單例模式要根據具體情況而定. 我個人認為, 重要的不是實現方式, 而是實現方式背後單例模式的思想.

Python中代替單例模式的方法——>Borg

Borg(也叫做Monostate)是由Alex Martelli提出的一種實現類似單例模式行為的寫法, 它並不是傳統設計模式中的一種.

這種寫法的核心思想是: 不限制一個類有多少的例項, 只要類的所有例項的狀態都是共享的, 同樣可以實現類似單例模式中的統一性.

具體實現就是在使用Borg例項化新物件時, 所有物件都共享一個__dict__.

使用Borg也可以解決錯誤實現方式中的繼承問題. 但是Borg寫法也有著自己的隱患. 當繼承Borg的子類中重寫了__getattr__方法可能出現問題.

class Borg:
    _state = {}
    
    
    def __new__(cls, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        obj.__dict__ = cls._state
        return obj
Borg示例
class DatabaseConnector:
    _state = {}
    
    
    def __new__(cls, db, *args, **kwargs):
        obj = super().__new__(cls, *args, **kwargs)
        obj.__dict__ = cls._state
        return obj
    
    
    def __init__(self, db):
        self.db = db
        
        
if __name__ == "__main__":
    d1 = DatabaseConnector("MySQL")
    d2 = DatabaseConnector("PostgreSQL")
    print(d1.db) # PostgreSQL
    print(d2.db) # PostgreSQL

使用模組實現單例模式

Python中的模組可以用於代替上面提到的幾種寫法實現單例模式, 因為Python中的模組本身已經是單例了.

最常見的用法就是在模組中例項化需要作為單例使用的物件並賦值給模組中的作用域是整個模組的變數, 然後開發者在需要時直接從模組中匯入這個物件來使用而不自己手動例項化.

這種寫法相對而言並不穩妥, 因為我們可以隨時給指向單例物件的變數重新賦值, 也依然可以直接例項化物件.

但是換個角度來看, 這種做法也讓我們的程式碼更加的靈活, 我們可以寫文件註釋來提示其他開發者某些物件應該作為單例來使用, 但當他們有其它需求時也可以直接例項化建立多個物件。

# Python的模組是天然的單例模式。
# module_name.py
class MySingleton(object):
    def foo(self):
        print('danli')

        
my_singleton = MySingleton()

# to use
from .module_name import my_singleton
my_singleton.foo()

print(id(my_singleton))

from .module_name import my_singleton
my_singleton.foo()

print(id(my_singleton))

6. 原型模式(Prototype)

內容:

用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。在Python中最簡單的原型模式可以使用copy模組中的deepcopy函式來實現,它接收一個物件作為引數,然後返回這個物件的拷貝物件。正是因為在Python中拷貝是內建功能,使用起來非常方便和自然,因此在使用時也較少被稱為一種模式。

使用場景:

  • 通過動態裝載;
  • 為了避免建立一個與產品類層次平行的工廠類層次時;
  • 當一個類的例項只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工例項化該類更方便一些。

img

  • 當系統中已有一個物件時,如果需要修改它的一些屬性,但同時我們不想直接對它做出修改,此時就可以使用原型模式。以已有物件作為原型建立拷貝,然後修改和使用拷貝的物件。

    比如在Django中, HttpRequest物件中的GETPOSTQueryDict型別的物件,它預設是不可變的。 如圖所示:

    雖然HttpRequest的初始化方法中建立QueryDict時設定為了可變,但在檢視中修改request.GET時會報錯。

    所以當我們需要修改request.GET時,就可以建立一份拷貝。拷貝得到的物件是可變的,我們可以對拷貝的物件進行修改後使用。使用QueryDict物件的copy方法就可以建立拷貝。從原始碼截圖中可以看出,copy`方法內部呼叫的還是Python內建的深拷貝。

原型模式示例程式碼:

import copy


class Prototype:
    def __init__(self):
        self._objects = {}

    def register_object(self, name, obj):
        """Register an object"""
        self._objects[name] = obj

    def unregister_object(self, name):
        """Unregister an object"""
        del self._objects[name]

    def clone(self, name, **attr):
        """Clone a registered object and update inner attributes dictionary"""
        obj = copy.deepcopy(self._objects.get(name))
        obj.__dict__.update(attr)
        return obj


def main():
    class A:
        def __str__(self):
            return "I am A"

    a = A()
    prototype = Prototype()
    prototype.register_object('a', a)
    b = prototype.clone('a', a=1, b=2, c=3)

    print(a)
    print(b.a, b.b, b.c)


if __name__ == '__main__':
    main()

建立型模式總結

使用抽象工廠(Abstract Factory)、原型(Prototype)或者建造者(Builder)的設計甚至比工廠方法(Factory Method)的那些設計更靈活,但它們也更加複雜。通常,設計以使用工廠方法(Factory Method)開始。並且當設計者發現需要更大的靈活性時,設計便會想其他建立模式煙花。當你在設計標準之間權衡的時候,瞭解多個模式可以給你提供給更多的選擇餘地。

依賴於繼承的建立型模式:工廠方法模式

依賴於組合的建立型模式:抽象工廠模式、建立者模式


二、結構性模式

這些設計模式在大型應用中十分重要,它們為開發者提供了劃分程式碼組織結構的方法,以及如何讓應用中的各個部分協同工作的方法。

1. 介面卡模式(Adapter Class/Object)

內容:將一個類的介面轉換成客戶希望的另一個介面。介面卡模式使得原本由於介面不相容而不能一起工作的那些類可以一起工作。
角色

  • 目標介面(Target)
  • 待適配的類(Adaptee)
  • 介面卡(Adapter)

兩種實現方式

  • 類介面卡:使用多繼承
  • 物件介面卡:使用組合

適用場景

  • 想使用一個已經存在的類,而它的介面不符合你的要求
  • (物件介面卡)想使用一些已經存在的子類,但不可能對每一個都進行子類化以匹配它們的介面。物件介面卡可以適配它的父類介面。

類介面卡

  • 用一個具體的Adapter類對Adaptee和Target進行匹配。結果是當我們想要匹配一個類以及所有他的子類時,類Adaptee將不能勝任工作。
  • 使得Adapter可以重定義Adaptee的部分行為,因為Adapter是Adaptee的一個子類。
  • 僅僅引入一個物件,並不需要額外的指標以間接得到Adaptee。

物件介面卡

  • 允許一個Adapter與多個Adaptee——即Adaptee本身以及它所有的子類(如果有子類的話)一同時工作。Adapter也可以一次給所有的Adaptee新增功能。
  • 使得重定義Adaptee的行為比較困難。這酒需要生成Adaptee的子類並且使得Adapter引用這個子類而不是引用Adaptee本身。

img

介面卡模式示例程式碼:

from abc import abstractmethod, ABCMeta


class Payment(metaclass=ABCMeta):
    @abstractmethod
    def pay(self, money):
        raise NotImplementedError


class Alipay(Payment):
    def pay(self, money):
        print("支付寶支付%s元" % money)


class ApplePay(Payment):
    def pay(self, money):
        print("蘋果支付%s元" % money)


# ------待適配類------

class WechatPay:
    def cost(self, money):
        print("微信支付%s元" % money)


# 類介面卡
class RealWechatPay(WechatPay, Payment):
    def pay(self, money):
        return self.cost(money)


# 物件介面卡
class RealWechatPay2(Payment):
    def __init__(self):
        self.payment = WechatPay()

    def pay(self, money):
        return self.payment.cost(money)


p = RealWechatPay2()
p.pay(111)

2. 組合模式(Composite)

內容:將物件組合成樹形結構以表示“部分-整體”的層次結構。組合模式使得使用者對單個物件和組合物件的使用具有一致性。
角色

  • 抽象元件(Component)
  • 葉子元件(Leaf)
  • 複合元件(Composite)
  • 客戶端(Client)

適用場景

  • 表示物件的“部分-整體”層次結構(特別是結構是遞迴的)
  • 希望使用者忽略組合物件與單個物件的不同,使用者統一地使用組合結構中的所有物件

優點

  • 定義了包含基本物件和組合物件的類層次結構
  • 簡化客戶端程式碼,即客戶端可以一致地使用組合物件和單個物件
  • 更容易增加新型別的元件

缺點

  • 很難限制組合中的元件

img

組合模式示例程式碼:

from abc import abstractmethod, ABCMeta


class Graphic(metaclass=ABCMeta):
    
    @abstractmethod
    def draw(self):
        pass

    @abstractmethod
    def add(self, graphic):
        pass

    def getchildren(self):
        pass

    
# 圖元
class Point(Graphic):
    
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def draw(self):
        print(self)

    def add(self, graphic):
        raise TypeError

    def getchildren(self):
        raise TypeError

    def __str__(self):
        return "點(%s, %s)" % (self.x, self.y)


class Line(Graphic):
    
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2

    def draw(self):
        print(self)

    def add(self, graphic):
        raise TypeError

    def getchildren(self):
        raise TypeError

    def __str__(self):
        return "線段[%s, %s]" % (self.p1, self.p2)


class Picture(Graphic):
    
    def __init__(self):
        self.children = []

    def add(self, graphic):
        self.children.append(graphic)

    def getchildren(self):
        return self.children

    def draw(self):
        print("------複合圖形------")
        for g in self.children:
            g.draw()
        print("------END------")


pic1 = Picture()
point = Point(2,3)
pic1.add(point)
pic1.add(Line(Point(1,2), Point(4,5)))
pic1.add(Line(Point(0,1), Point(2,1)))

pic2 = Picture()
pic2.add(Point(-2,-1))
pic2.add(Line(Point(0,0), Point(1,1)))

pic = Picture()
pic.add(pic1)
pic.add(pic2)

pic.draw()
#pic1.draw()
#point.draw()

3. 裝飾模式

內容:

裝飾模式允許我們使用其它物件將提供核心功能的物件包裝起來, 藉此提供額外的功能或者修改原物件的行為和功能. 被裝飾後的物件應與原物件擁有相同的介面, 這樣允許我們在不影響程式碼其它部分的情況下自由選擇使用裝飾後的物件還是原物件. 裝飾是可以巢狀的, 由在最內層的物件提供核心功能.

裝飾模式與Python裝飾器:

首先要明確一點, 裝飾模式與Python中的裝飾器是不同的概念.

本篇文章僅討論裝飾模式, 以後也會整理髮布Python裝飾器相關的文章供大家參考.

裝飾模式是一種無關具體語言的設計模式, 而Python語言中的裝飾器是一種可呼叫物件, 它接收一個函式作為引數, 也會返回一個函式作為結果. 一般會將返回的函式重新賦值代替原函式來擴充套件函式, 方法或類的功能.

Python中的函式也是物件, 在Python中使用裝飾器裝飾函式其實就是在應用裝飾模式. Python開發者們經常會用到這種用法, 以至於Python為裝飾器提供了@decorator的語法糖來簡化這種寫法.

當對函式進行裝飾時, 一般都是為了永久的擴充套件或修改函式的功能, 所以都是在函式定義時, 而不是在執行時決定是否使用裝飾器. 使用語法糖的好處是可以很清晰的看出函式是被裝飾過的. 語法糖僅能用於我們自己的程式碼, 在想要裝飾第三方的程式碼時, 由於我們無法修改原始碼, 所以依然只能使用重新賦值的方式進行裝飾.

適用場景:

在需要擴充一些通用功能時可以考慮使用裝飾模式, 比如:

  • 校驗(資料, 許可權)
  • 快取
  • 記錄日誌
  • 加密
  • 程式排錯

在Django中就提供了許多檢視的裝飾器, 其中就有一些可以用來校驗訪問使用者的許可權或者快取檢視提高處理響應的效率.

裝飾模式與繼承:

以下討論適用於單繼承以及多繼承.

由於裝飾模式也可以給物件新增額外的功能, 有時也可以用裝飾模式來代替繼承.

對比使用繼承, 裝飾模式主要有兩大優勢, 或者說只有滿足以下兩點中的條件時才應該優先選擇裝飾模式:

  • 可以根據一些條件動態決定是否裝飾擴充套件物件的功能.
  • 同一功能可以擁有多個可選的擴充套件.

裝飾模式示例程式碼:

下面這個簡單的例子中, 根據debug以及log條件的值, 最終的adder物件的行為也會發生變化. 當兩個值都為True時, 最終的adder物件是經過了兩層裝飾後的物件, 在呼叫add方法時既會在控制檯中列印, 也會寫入日誌檔案.

假如我們只需要寫入日誌的額外功能, 即只需要LogAdder類, 那麼LogAdder類也可以使用繼承Adder類的方式來實現. 如上文中提到的, 當有多個可選擴充套件和/或需要動態決定是否使用擴充套件功能時, 使用裝飾模式更加簡便.

class Adder:
    def add(self, a, b):
        return a + b
    
    
class PrintAdder:
    def __init__(self, adder):
        self.adder = adder
    def add(self, a, b):
        res = self.adder.add(a, b)
        print(f"{a} + {b} = {res}")
        return res
    
    
class LogAdder:
    def __init__(self, adder):
        self.adder = adder
    def add(self, a, b):
        res = self.adder.add(a, b)
        with open("adder.log", "w") as f:
            f.write(f"{a} + {b} = {res}\n")
        return res
    
    
if __name__ == "__main__":
    debug = True
    log = True
    adder = Adder()
    if debug:
        adder = PrintAdder(adder)
    if log:
        adder = LogAdder(adder)
    adder.add(1, 1)
    print("from log: ", end="")
    with open("adder.log", "r") as f:
        print(f.read())
    """
    1 + 1 = 2
    from log: 1 + 1 = 2
    """

上面的例子主要為了展示裝飾模式的思想, 在Python中可以使用裝飾器更簡單的完成上面的例子, 一般也更推薦使用那種方式.

4. 橋接模式

內容:

橋接模式與介面卡模式看起來十分相似, 它們的區別在於: 介面卡模式是用在已有程式碼上讓它們一起工作, 而橋接模式用在開始寫其它程式碼之前.

橋接模式可以讓我們將抽象部分與具體實現部分分開, 用組合代替繼承, 這樣後續調整擴充套件抽象部分或實現部分時更簡單.

Python中的橋接模式中主要有兩類角色:

  • 具體實現化角色: 實現在抽象化角色中呼叫的介面
  • 抽象化角色: 內部包含具體實現化角色, 通過呼叫它實現的介面來實現業務邏輯

當有需要時也可以按照傳統的橋接模式定義四類角色來實現(參考http://c.biancheng.net/view/1364.html):

  • 抽象化(Abstraction)角色
  • 擴充套件抽象化(Refined Abstraction)角色
  • 實現化(Implementor)角色
  • 具體實現化(Concrete Implementor)角色

上面的內容可能不太好理解, 看看示例程式碼就容易理解了.

適用場景:

想要在多個物件中共享實現時可以使用橋接模式.

橋接模式示例程式碼:

下面的程式碼中的IOPrinter是抽象化角色, 它目前擁有兩個功能: 從指定I/O讀取全部資料或者預覽I/O中的部分內容. 這兩個功能的實現被所有具體實現化角色共享.

StdinReader是具體實現化角色, 它內部實現了抽象化角色需要呼叫的read()方法, 同時Python內建的檔案物件中也有read()方法, 所以也可以作為一種具體實現.

class IOPrinter:
    
    def __init__(self, reader):
        self.reader = reader
        
    def print_all(self):
        print(self.reader.read())
        
    def preview(self):
        print(self.reader.read()[:5], "...")
        
        
class StdinReader:
    def read(self):
        return input("stdin: ")
    
    
if __name__ == "__main__":
    with open("file.txt", "r") as f:
        printer = IOPrinter(f)
        printer.print_all()
    printer = IOPrinter(StdinReader())
    printer.preview()

這段程式碼也展示了使用橋接模式帶來的程式擴充套件性, 我們可以通過擴充套件抽象化角色新增新的功能, 比如增加倒序列印, 同時也可以通過建立新的具體實現化角色來新增新的功能, 比如從網路上或從資料庫讀取資料.

5. 代理模式 (Proxy)

內容:為其他物件提供一種代理以控制對這個物件的訪問。
角色

  • 抽象實體(Subject)
  • 實體(RealSubject)
  • 代理(Proxy)

適用場景

  • 遠端代理:為遠端的物件提供代理
  • 虛代理:根據需要建立很大的物件
  • 保護代理:控制對原始物件的訪問,用於物件有不同訪問許可權時

優點

  • 遠端代理:可以隱藏物件位於遠端地址空間的事實
  • 虛代理:可以進行優化,例如根據要求建立物件
  • 保護代理:允許在訪問一個物件時有一些附加的內務處理

img

代理模式示例程式碼:

from abc import ABCMeta, abstractmethod


class Subject(metaclass=ABCMeta):
    @abstractmethod
    def get_content(self):
        pass

    def set_content(self, content):
        pass


class RealSubject(Subject):
    def __init__(self, filename):
        self.filename = filename
        print("讀取%s檔案內容" % filename)
        f = open(filename)
        self.__content = f.read()
        f.close()

    def get_content(self):
        return self.__content

    def set_content(self, content):
        f = open(self.filename, 'w')
        f.write(content)
        self.__content = content
        f.close()


# ---遠端代理

class ProxyA(Subject):
    def __init__(self, filename):
        self.subj = RealSubject(filename)

    def get_content(self):
        return self.subj.get_content()

    def set_content(self, content):
        return self.subj.set_content(content)


# ---虛代理

class ProxyB(Subject):
    def __init__(self, filename):
        self.filename = filename
        self.subj = None

    def get_content(self):
        if not self.subj:
            self.subj = RealSubject(self.filename)
        return self.subj.get_content()


x = ProxyB('abc.txt')
# print(x.get_content())

# ---保護代理

class ProxyC(Subject):
    def __init__(self, filename):
        self.subj = RealSubject(filename)

    def get_content(self):
        self.subj.get_content()

    def set_content(self, content):
        raise PermissionError

# filename = "abc.txt"
# username = input()
# if username!="alex":
#     p = ProxyC(filename)
# else:
#     p = ProxyA(filename)
#
# print(p.get_content())


6. MVC模式

內容:

MVC其實是一種架構模式而不是一種設計模式, 兩者的區別就是前者的應用範圍更加廣泛. 因為這個模式太重要了, 又和設計模式有一定關聯, 於是決定放在一起討論.

MVC模式是軟體設計中的SoC(Separation of Concerns)原則應用在物件導向程式設計模式中的一種體現. SoC原則大致可以理解為: 將程式分成不同的部分, 每個部分都都有自己明確的分工, 每個部分只關注負責自己的分工.

MVC模式將應用程式分成三大部分: 模型(model), 檢視(view)和控制器(controller). 這個模式的名稱也來源於這三大部分.

  • 模型是這三部分中的核心, 它包含負責程式的狀態, 資料和業務邏輯.
  • 檢視不負責處理資料, 它只負責對模型的展示(當有使用者互動時也負責使用者互動). 檢視可以有著多種形式, 比如圖形使用者介面, 統計圖表, 純文字等.
  • 控制器是連線模型和檢視之間的橋樑, 模型和檢視之間的互動都通過它完成.

使用控制器作為傳話筒看起來有些冗餘, 但它有著存在的必要: 使用控制器可以在不修改模型的情況下讓它擁有多個檢視. 一般而言, 想要做到這一點的話每個檢視都應該有著自己的控制器.

應用MVC模式的程式的典型使用流程:

  1. 使用者的一些操作(如點選按鈕)觸發了一個檢視.
  2. 檢視將使用者的動作告知控制器.
  3. 控制器處理使用者輸入並與模型互動.
  4. 模型進行資料的校驗, 執行相關的操作然後通知控制器該如何繼續.
  5. 控制器將結果傳遞給檢視, 檢視根據結果和指示更新顯示的資訊和資料.

優點與應用:

優點:

  • 模型與檢視的分離可以讓負責不同部分的開發者同時進行開發互不影響.
  • 模型與檢視耦合性低, 修改擴充套件已有的模型和檢視時不會相互干擾. 新增新的檢視也很簡單.
  • 每部分分工明確, 易於後期維護。

應用:

一般來講我們不需要從零開始實現MVC架構而是直接使用已有的應用了MVC架構或其變種的框架.

MVC或者它的變種在框架中非常常見, 比如Django框架就使用了從MVC演變來的MTV(Model-Template-View)模式.

名稱對照表:

傳統MVC模式 Django MTV模式
model model
view template
controller view

Django的設計者認為檢視(view)描述控制著什麼(what)資料可以被使用者看到, 所以他使用檢視來命名負責這個功能的元件. 而模板(template)在Django中負責展示資料, 它決定資料是怎樣(how)展示給使用者.

從零開始實現MVC模式

當已有的框架都不能滿足我們的需求時, 我們也可以自己實現MVC模式.

設計原則

當從零開始實現MVC時, 模型, 檢視和控制器需要滿足以下原則.

模型

  • 獨立於展示方式.
  • 包含所有的校驗邏輯以及業務邏輯.
  • 可以訪問程式中的資料(資料庫, 檔案等).
  • 負責更新應用程式的狀態.

檢視

  • 展示資料.
  • 允許使用者互動.
  • 不包含任何資料校驗以及業務邏輯. 只包含最低限度的其它邏輯, 比如模板語言中的迴圈和分支等.
  • 不能直接獲取程式中的資料.
  • 不儲存任何資料.

控制器

  • 模型發生改變時負責更新檢視.
  • 檢視與使用者有互動時負責更新模型.
  • 不包含任何資料校驗以及業務邏輯.
  • 不能直接獲取程式中的資料.
  • 不顯示任何資料.
  • 當需要時, 在在模型和檢視之間傳遞資料之前處理資料.

當我們想要確認是否正確的實現了MVC模式時, 可以試著回答以下問題:

  • 如果程式有圖形介面的話, 可以給圖形介面換皮膚嗎? 換皮膚簡單嗎? 增加讓使用者可以在執行過程中給介面換皮膚的功能困難嗎? 如果不是很簡單的話, 那麼MVC模式沒有被正確的實現.
  • 假如應用沒有圖形介面的話, 為它新增一個圖形介面容易嗎? 假如沒必要新增圖形介面, 那麼新增其它形式的檢視容易嗎? 比如檔案, 圖片或圖表. 假如無法做到只靠新增新的檢視和控制器而不修改模型來實現, 那麼證明有些地方的實現有問題.

三、行為模式

1. 責任鏈模式(Chain of Responsibility)

內容

使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有一個物件處理它為止。
角色

  • 抽象處理者(Handler)
  • 具體處理者(ConcreteHandler)
  • 客戶端(Client)

例:

  • 請假部門批准:leader—>部門經理—>總經理
  • Javascript事件浮升機制

適用場景

  • 有多個物件可以處理一個請求,哪個物件處理由執行時決定
  • 在不明確接收者的情況下,向多個物件中的一個提交一個請求

優點

  • 降低耦合度:一個物件無需知道是其他哪一個物件處理其請求

缺點

  • 請求不保證被接收:鏈的末端沒有處理或鏈配置錯誤

img

請假流程:

from abc import ABCMeta, abstractmethod


class Handler(metaclass=ABCMeta):
    @abstractmethod
    def handle_leave(self, day):
        pass


class GeneralManagerHandler(Handler):
    def handle_leave(self, day):
        if day < 10:
            print("總經理批准%d天假" % day)
            return True
        else:
            print("呵呵")
            return False


class DepartmentManagerHandler(Handler):
    def __init__(self):
        self.successor = GeneralManagerHandler()

    def handle_leave(self, day):
        if day < 7:
            print("部門經理批准%d天假" % day)
            return True
        else:
            print("部門經理無權准假")
            return self.successor.handle_leave(day)


class ProjectDirectorHandler(Handler):
    def __init__(self):
        self.successor = DepartmentManagerHandler()

    def handle_leave(self, day):
        if day < 3:
            print("專案主管批准%d天假" % day)
            return True
        else:
            print("專案主管無權准假")
            return self.successor.handle_leave(day)


day = 11
h = ProjectDirectorHandler()
print(h.handle_leave(day))

模仿js事件處理

# --高階例子--模仿js事件處理
from abc import ABCMeta, abstractmethod

class Handler(metaclass=ABCMeta):
    @abstractmethod
    def add_event(self, func):
        pass

    @abstractmethod
    def handle(self):
        pass


class BodyHandler(Handler):
    def __init__(self):
        self.func = None

    def add_event(self, func):
        self.func = func

    def handle(self):
        if self.func:
            return self.func()
        else:
            print("已到最後一級,無法處理")


class ElementHandler(Handler):
    def __init__(self, successor):
        self.func = None
        self.successor = successor

    def add_event(self, func):
        self.func = func

    def handle(self):
        if self.func:
            return self.func()
        else:
            return self.successor.handle()


# 客戶端

# <body><div><a>

body = {'type': 'body', 'name': 'body', 'children': [], 'father': None}

div = {'type': 'div', 'name': 'div', 'children': [], 'father': body}

a = {'type': 'a', 'name': 'a', 'children': [], 'father': div}

body['children'].append(div)
div['children'].append(a)

# print(body)


body['event_handler'] = BodyHandler()
div['event_handler'] = ElementHandler(div['father']['event_handler'])
a['event_handler'] = ElementHandler(a['father']['event_handler'])


def attach_event(element, func):
    element['event_handler'].add_event(func)


# test

def func_a():
    print("這是給a的函式")


def func_div():
    print("這是給div的函式")


def func_body():
    print("這是給body的函式")


attach_event(a, func_a)
attach_event(div, func_div)
attach_event(body, func_body)

a['event_handler'].handle()

2. 迭代器模式(Iterator)

內容

提供一種方法順序訪問一個聚合物件中的各個元素,而又不需要暴露該物件的內部表示。

適用場景:

  • 訪問一個聚合物件的內容而無需暴露它的內部表示。
  • 支援對聚合物件的多種遍歷。
  • 為遍歷不同的聚合結構提供一個統一的介面(即, 支援多型迭代)

實現方法iternext

img

連結串列:

class LinkList:
    """連結串列 頭結點儲存連結串列的長度"""

    class Node:
        def __init__(self, item=None):
            self.item = item
            self.next = None

    class LinkListIterator:
        def __init__(self, node):
            self.node = node

        def __next__(self):
            if self.node:
                cur_node = self.node
                self.node = cur_node.next
                return cur_node.item
            else:
                raise StopIteration

        def __iter__(self):
            return self

    def __init__(self, iterable=None):
        self.head = LinkList.Node(0)
        self.tail = self.head
        self.extend(iterable)

    def append(self, obj):
        s = LinkList.Node(obj)
        self.tail.next = s
        self.tail = s
        self.head.item += 1

    def extend(self, iterable):
        for obj in iterable:
            self.append(obj)

    def __iter__(self):
        return self.LinkListIterator(self.head.next)

    def __len__(self):
        return self.head.item

    def __str__(self):
        return "<<" + ", ".join(map(str, self)) + ">>"


li = [i for i in range(100)]
lk = LinkList(li)
print(lk)

3. 觀察者模式(Observer)

內容

定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時, 所有依賴於它的物件都得到通知並被自動更新。觀察者模式又稱“釋出-訂閱”模式
角色

  • 抽象主題(Subject)
  • 具體主題(ConcreteSubject)——釋出者
  • 抽象觀察者(Observer)
  • 具體觀察者(ConcreteObserver)——訂閱者

適用場景

  • 當一個抽象模型有兩方面,其中一個方面依賴於另一個方面。將這兩者封裝在獨立物件中以使它們可以各自獨立地改變和複用。
  • 當對一個物件的改變需要同時改變其它物件,而不知道具體有多少物件有待改變。
  • 當一個物件必須通知其它物件,而它又不能假定其它物件是誰。換言之,你不希望這些物件是緊密耦合的。

優點

  • 目標和觀察者之間的抽象耦合最小
  • 支援廣播通訊

缺點

  • 多個觀察者之間互不知道對方存在,因此一個觀察者對主題的修改可能造成錯誤的更新。

img

釋出者——訂閱者:

from abc import ABCMeta, abstractmethod


class Observer(metaclass=ABCMeta):
    @abstractmethod
    def update(self, notice):
        pass


class Notice:
    def __init__(self):
        self.observers = []

    def attach(self, obs):
        self.observers.append(obs)

    def detach(self, obs):
        self.observers.remove(obs)
        # obs.company_info=None

    def notify(self):
        for obj in self.observers:
            obj.update(self)


class ManagerNotice(Notice):
    def __init__(self, company_info=None):
        super().__init__()
        self.__company_info = company_info

    def detach(self, obs):
        super().detach(obs)
        obs.company_info = None

    @property
    def company_info(self):
        return self.__company_info

    @company_info.setter
    def company_info(self, info):
        self.__company_info = info
        self.notify()


class Manager(Observer):
    def __init__(self):
        self.company_info = None

    def update(self, noti):
        self.company_info = noti.company_info


notice = ManagerNotice()

alex = Manager()
wusir = Manager()

print(alex.company_info)
print(wusir.company_info)

notice.attach(alex)
notice.attach(wusir)
notice.company_info = "公司執行良好"

print(alex.company_info)
print(wusir.company_info)

notice.company_info = "公司將要上市"

print(alex.company_info)
print(wusir.company_info)

notice.detach(wusir)

notice.company_info = "公司要破產了,趕快跑路"

print(alex.company_info)
print(wusir.company_info)

notice.company_info = "公司已經破產了"

print(alex.company_info)
print(wusir.company_info)

4. 策略模式(Strategy)

內容

定義一系列的演算法,把它們一個個封裝起來,並且使它們可相互替換。本模式使得演算法可獨立於使用它的客戶而變化。
角色

  • 抽象策略(Strategy)
  • 具體策略(ConcreteStrategy)
  • 上下文(Context)

適用場景

  • 許多相關的類僅僅是行為有異
  • 需要使用一個演算法的不同變體
  • 演算法使用了客戶端無需知道的資料
  • 一個類中的多種行為以多個條件語句的形式存在,可以將這些行為封裝如不同的策略類中。

優點

  • 定義了一系列可重用的演算法和行為
  • 消除了一些條件語句
  • 可以提供相同行為的不同實現

缺點

  • 客戶必須瞭解不同的策略
  • 策略與上下文之間的通訊開銷
  • 增加了物件的數目

img

策略模式示例程式碼:

from abc import ABCMeta, abstractmethod
import random


class Sort(metaclass=ABCMeta):
    @abstractmethod
    def sort(self, data):
        pass


class QuickSort(Sort):
    def quick_sort(self, data, left, right):
        if left < right:
            mid = self.partition(data, left, right)
            self.quick_sort(data, left, mid - 1)
            self.quick_sort(data, mid + 1, right)

    def partition(self, data, left, right):
        tmp = data[left]
        while left < right:
            while left < right and data[right] >= tmp:
                right -= 1
            data[left] = data[right]
            while left < right and data[left] <= tmp:
                left += 1
            data[right] = data[left]
        data[left] = tmp
        return left

    def sort(self, data):
        print("快速排序")
        return self.quick_sort(data, 0, len(data) - 1)


class MergeSort(Sort):
    def merge(self, data, low, mid, high):
        i = low
        j = mid + 1
        ltmp = []
        while i <= mid and j <= high:
            if data[i] <= data[j]:
                ltmp.append(data[i])
                i += 1
            else:
                ltmp.append(data[j])
                j += 1

        while i <= mid:
            ltmp.append(data[i])
            i += 1

        while j <= high:
            ltmp.append(data[j])
            j += 1

        data[low:high + 1] = ltmp

    def merge_sort(self, data, low, high):
        if low < high:
            mid = (low + high) // 2
            self.merge_sort(data, low, mid)
            self.merge_sort(data, mid + 1, high)
            self.merge(data, low, mid, high)

    def sort(self, data):
        print("歸併排序")
        return self.merge_sort(data, 0, len(data) - 1)


class Context:
    def __init__(self, data, strategy=None):
        self.data = data
        self.strategy = strategy

    def set_strategy(self, strategy):
        self.strategy = strategy

    def do_strategy(self):
        if self.strategy:
            self.strategy.sort(self.data)
        else:
            raise TypeError


li = list(range(100000))
random.shuffle(li)

context = Context(li, MergeSort())
context.do_strategy()
# print(context.data)

random.shuffle(context.data)

context.set_strategy(QuickSort())
context.do_strategy()

5. 模板方法(Template Method)

內容

定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
角色

  • 抽象類(AbstractClass):定義抽象的原子操作(鉤子操作);實現一個模板方法作為演算法的骨架。
  • 具體類(ConcreteClass):實現原子操作

適用場景

  • 一次性實現一個演算法的不變的部分
  • 各個子類中的公共行為應該被提取出來並集中到一個公共父類中以避免程式碼重複
  • 控制子類擴充套件

img

模板方法示例程式碼:

from abc import ABCMeta, abstractmethod


class IOHandler(metaclass=ABCMeta):
    @abstractmethod
    def open(self, name):
        pass

    @abstractmethod
    def deal(self, change):
        pass

    @abstractmethod
    def close(self):
        pass

    def process(self, name, change):
        self.open(name)
        self.deal(change)
        self.close()


class FileHandler(IOHandler):
    def open(self, name):
        self.file = open(name, "w")

    def deal(self, change):
        self.file.write(change)

    def close(self):
        self.file.close()


f = FileHandler()
f.process("abc.txt", "Hello World")

相關文章