原型模式允許開發者根據現有物件建立新物件,並繼承其狀態,避免重複初始化。單例模式則限制類別例項數量為一,提供全域唯一存取點,常用於管理共用資源或全域狀態。Python 提供了簡潔的語法實作這兩種模式。透過 copy.deepcopy() 函式,原型模式得以深度複製物件狀態,確保新物件的獨立性。單例模式則可利用元類別(metaclass)或類別變數控制例項化過程,確保唯一性。在網頁抓取程式中,單例模式能有效管理已抓取 URL 清單,避免重複抓取,同時簡化程式碼結構。

實作原型模式

在這個章節中,我們將探討如何實作原型模式。原型模式是一種建立型模式,允許你建立一個新的物件,同時保留原始物件的狀態。

以下是實作原型模式的步驟:

  1. 定義一個基礎類別,包含一個 clone() 方法。
  2. 定義一個子類,繼承基礎類別,並實作 clone() 方法。
  3. 建立一個原型物件,並設定其狀態。
  4. 使用 clone() 方法建立一個新的物件,同時保留原始物件的狀態。

以下是 Python 中實作原型模式的例子:

import copy

class Website:
    def __init__(self, name, author, description, keywords):
        self.name = name
        self.author = author
        self.description = description
        self.keywords = keywords

    def __str__(self):
        return f"Website '{self.name}'"

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

    def register(self, name, obj):
        self._objects[name] = obj

    def unregister(self, name):
        del self._objects[name]

    def clone(self, name, **attrs):
        obj = copy.deepcopy(self._objects.get(name))
        obj.__dict__.update(attrs)
        return obj

def main():
    keywords = ('python', 'data', 'apis', 'automation')
    site1 = Website("ContentGardening", "玄貓", "Automation and data-driven apps", keywords)

    prototype = Prototype()
    prototype.register("site1", site1)

    site2 = prototype.clone("site1", name="ContentGardeningPlayground", author="玄貓: Membership site", creation_date="2018-08-01")

    print(site1)
    print(site2)

if __name__ == "__main__":
    main()

實作單例模式

單例模式是一種建立型模式,限制一個類別只能建立一個例項。以下是實作單例模式的步驟:

  1. 定義一個類別,包含一個私有屬性 _instance
  2. 定義一個靜態方法 get_instance(),傳回 _instance
  3. 定義一個 __init__() 方法,設定 _instance

以下是 Python 中實作單例模式的例子:

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

def main():
    singleton1 = Singleton()
    singleton2 = Singleton()

    print(singleton1 is singleton2)  # True

if __name__ == "__main__":
    main()

圖表翻譯:

  classDiagram
    class Website {
        - name: str
        - author: str
        - description: str
        - keywords: tuple
        + __init__(name: str, author: str, description: str, keywords: tuple)
        + __str__() str
    }

    class Prototype {
        - _objects: dict
        + register(name: str, obj: Website)
        + unregister(name: str)
        + clone(name: str, **attrs) Website
    }

    class Singleton {
        - _instance: Singleton
        + get_instance() Singleton
        + __init__()
    }

內容解密:

  1. 原型模式允許你建立一個新的物件,同時保留原始物件的狀態。
  2. 單例模式限制一個類別只能建立一個例項。
  3. 原型模式和單例模式都是建立型模式,用於控制物件的建立。
  4. 原型模式使用 clone() 方法建立新的物件,而單例模式使用靜態方法 get_instance() 傳回唯一的例項。

實作單例模式的網頁抓取程式

在這個例子中,我們將使用 Python 的 urllib 模組來連線網頁並抓取其內容。程式的核心將是 URLFetcher 類別,負責執行抓取工作。

需求分析

我們需要一個單例模式的物件來維護全域狀態,追蹤已經抓取的網頁清單。這個單例模式的物件將被稱為 URLFetcher

實作單例模式

import urllib.parse
import urllib.request

class URLFetcher:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(URLFetcher, cls).__new__(cls)
            cls._instance.urls = []
        return cls._instance

    def fetch(self, url):
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as response:
            the_page = response.read()
            print(the_page)
            self.urls.append(url)

# 測試單例模式
fetcher1 = URLFetcher()
fetcher2 = URLFetcher()

print(fetcher1 is fetcher2)  # True

fetcher1.fetch("https://www.example.com")
print(fetcher1.urls)  # ['https://www.example.com']

fetcher2.fetch("https://www.google.com")
print(fetcher2.urls)  # ['https://www.example.com', 'https://www.google.com']

在這個例子中,URLFetcher 類別實作了單例模式,使用 _instance 類別變數來儲存唯一的例項。__new__ 方法負責建立或傳回現有的例項。

結果分析

由於 URLFetcher 類別實作了單例模式,fetcher1fetcher2 是同一個例項。因此,當我們呼叫 fetcher1.fetch()fetcher2.fetch() 時,兩個例項都會存取同一個 urls 清單。

這個例子展示了單例模式的應用,使用單例模式來維護全域狀態和追蹤已經抓取的網頁清單。

實作單例模式

在設計模式中,單例模式是一種常見的模式,確保一個類別只有一個例項,並提供一個全域存取點。下面我們將實作單例模式,並將其應用於 URLFetcher 類別。

第一步:檢查當前的實作

首先,我們需要檢查當前的 URLFetcher 類別是否已經實作單例模式。為此,我們可以使用以下程式碼:

if __name__ == '__main__':
    f1 = URLFetcher()
    f2 = URLFetcher()
    print(f1 is f2)

或使用更簡潔的形式:

print(URLFetcher() is URLFetcher())

執行此程式碼後,若輸出為 False,則表示當前的實作尚未實作單例模式。

第二步:實作單例模式

為了實作單例模式,我們可以使用元類別(metaclass)技術。首先,定義一個元類別 SingletonType

class SingletonType(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

此元類別 _instances 字典儲存了所有例項,__call__ 方法負責建立和傳回例項。

第三步:修改 URLFetcher 類別

現在,我們需要修改 URLFetcher 類別以使用 SingletonType 元類別:

class URLFetcher(metaclass=SingletonType):
    # ...

此外,我們可以新增一個 dump_url_registry 方法,以便取得目前追蹤的 URL 清單:

class URLFetcher(metaclass=SingletonType):
    # ...

    def dump_url_registry(self):
        # ...

結果

經過以上步驟後,URLFetcher 類別現在已經實作單例模式。無論您建立多少例項,始終只會有一個例項存在。您可以使用以下程式碼驗證:

if __name__ == '__main__':
    f1 = URLFetcher()
    f2 = URLFetcher()
    print(f1 is f2)  # 輸出:True

現在,f1f2 是相同的例項,表示單例模式已經成功實作。

完整的 URLFetcher 程式

import urllib.request

class URLFetcher:
    def __init__(self):
        self.urls = []

    def fetch(self, url):
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as response:
            the_page = response.read()
            print(the_page)
        self.urls.append(url)

    def dump_url_registry(self):
        return ', '.join(self.urls)

def main():
    fetcher = URLFetcher()
    fetcher.fetch("http://example.com")
    print(fetcher.dump_url_registry())

if __name__ == '__main__':
    main()

內容解密:

上述程式碼定義了一個 URLFetcher 類別,負責從指定的 URL 下載內容並儲存下載過的 URL。fetch 方法使用 urllib.request 下載指定 URL 的內容,並將下載過的 URL 加入 urls 清單中。dump_url_registry 方法則回傳所有下載過的 URL,以逗號分隔的字串形式呈現。

main 函式中,我們建立了一個 URLFetcher 例項,下載 http://example.com 的內容,並印出下載過的 URL 清單。

圖表翻譯:

  flowchart TD
    A[建立 URLFetcher 例項] --> B[下載 URL 內容]
    B --> C[儲存下載過的 URL]
    C --> D[印出下載過的 URL 清單]

此圖表描述了 URLFetcher 類別的運作流程,從建立例項、下載 URL 內容、儲存下載過的 URL,到印出下載過的 URL 清單。

實作單例模式

單例模式是一種建立型模式,限制一個類別只能例項化一次。下面是使用Python實作單例模式的例子。

Singleton 類別

class SingletonType:
    def __call__(self, *args, **kwargs):
        if not hasattr(self, '_instance'):
            self._instance = super(SingletonType, self).__call__(*args, **kwargs)
        return self._instance

class URLFetcher(metaclass=SingletonType):
    def __init__(self):
        self.urls = []

    def fetch(self, url):
        # 實作URL抓取邏輯
        self.urls.append(url)

    def dump_url_registry(self):
        return self.urls

# 測試單例模式
print(URLFetcher() is URLFetcher())  # True

fetcher = URLFetcher()
MY_URLS = ['https://example.com', 'https://example.org']
for url in MY_URLS:
    try:
        fetcher.fetch(url)
    except Exception as e:
        print(e)

print('-------')
done_urls = fetcher.dump_url_registry()
print(f'Done URLs: {done_urls}')

程式解說

  1. 定義 SingletonType 類別,實作 __call__ 方法,限制類別只能例項化一次。
  2. 定義 URLFetcher 類別,繼承 SingletonType,實作 fetchdump_url_registry 方法。
  3. 測試單例模式,建立兩個 URLFetcher 例項,比較是否為同一個例項。
  4. 使用 URLFetcher 類別抓取 URL,並將抓取的 URL 儲存在 urls 屬性中。
  5. 使用 dump_url_registry 方法取得抓取的 URL 清單。

Mermaid 圖表

  classDiagram
    SingletonType <|-- URLFetcher
    class SingletonType {
        +__call__()
    }
    class URLFetcher {
        +__init__()
        +fetch(url)
        +dump_url_registry()
    }

圖表翻譯

此圖表展示了 SingletonTypeURLFetcher 類別之間的關係。SingletonType 類別實作了單例模式,限制類別只能例項化一次。URLFetcher 類別繼承 SingletonType,實作了 fetchdump_url_registry 方法,用於抓取 URL 和儲存抓取的 URL 清單。

什麼是介面卡模式?

介面卡模式是一種結構性設計模式,允許兩個不相容的軟體介面之間進行通訊。它提供了一種方法,使得具有不同介面的物件可以一起工作,從而增加了系統的靈活性和可重用性。

介面卡模式的優點

介面卡模式有以下幾個優點:

  • 提高系統的靈活性:介面卡模式允許系統中的不同元件之間進行通訊,即使它們具有不同的介面。
  • 增加可重用性:透過使用介面卡模式,系統中的元件可以被重用,即使它們具有不同的介面。
  • 減少系統的耦合度:介面卡模式可以減少系統中的耦合度,因為它允許系統中的不同元件之間進行通訊,而不需要修改它們的介面。

介面卡模式的實作

介面卡模式可以透過以下步驟實作:

  1. 定義目標介面:定義一個目標介面,這個介面是系統中其他元件所期望的介面。
  2. 定義被適配介面:定義一個被適配介面,這個介面是需要被適配的介面。
  3. 建立介面卡:建立一個介面卡,該介面卡實作了目標介面,並且可以與被適配介面進行通訊。
  4. 使用介面卡:使用介面卡使得系統中的不同元件可以一起工作。

介面卡模式的實際應用

介面卡模式在以下情況下非常有用:

  • 第三方函式庫的整合:當需要整合第三方函式庫時,介面卡模式可以用來使得第三方函式庫的介面與系統中的其他元件相容。
  • 不同系統的整合:當需要整合不同的系統時,介面卡模式可以用來使得不同系統的介面相容。
  • ** legacy 系統的整合**:當需要整合 legacy 系統時,介面卡模式可以用來使得 legacy 系統的介面與系統中的其他元件相容。

介面卡模式:讓不相容的介面相容

介面卡模式是一種結構設計模式,幫助我們使兩個不相容的介面相容。這意味著,如果我們有一個舊的元件想要在新的系統中使用,或者一個新的元件想要在舊的系統中使用,兩者之間的溝通往往需要進行程式碼修改。但是,並不是所有情況下都能夠修改程式碼,尤其是當我們沒有存取許可權或修改程式碼不切實際的情況下。這時候,我們可以寫一個額外的層來使兩個介面之間的溝通成為可能,這個層被稱為介面卡。

實際案例

當你從大多數歐洲國家旅行到英國或美國,或者反之亦然,你需要使用插頭介面卡來為你的筆記本充電。另一種介面卡是用於將某些裝置連線到你的電腦:USB介面卡。在軟體領域,Zope應用伺服器以其Zope元件架構(ZCA)而聞名,ZCA為實作介面和介面卡做出了貢獻,同時也被玄貓使用。Pyramid是一個由玄貓開發的Python網路框架,從Zope中汲取了良好的想法,以提供一個更模組化的方法來開發網路應用。Pyramid使用介面卡使現有的物件能夠遵守特定的API而無需修改它們。另一個來自Zope生態系統的專案,Plone CMS,在底層使用介面卡。

使用案例

通常,兩個不相容的介面中有一個是外來的或舊的。如果介面是外來的,意味著我們沒有存取其原始碼的許可權。如果它是舊的,則通常不切實際地重構它。使用介面卡來使事情在實作後正常工作是一種良好的方法,因為它不需要存取外來介面的原始碼。這也是一種實用的解決方案,如果我們需要重用一些舊的程式碼。

實作介面卡模式

讓我們透過一個相對簡單的應用來演示適配的概念。考慮一個俱樂部活動的例子,主要需要組織表演和活動來娛樂其客戶。核心上,我們有一個Club類,它代表了俱樂部,藝人在那裡表演。organize_performance方法是俱樂部可以執行的主要動作。程式碼如下:

class Club:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'俱樂部 {self.name}'

    def organize_event(self):
        return '聘請藝人為人們表演'

大多數時候,俱樂部聘請DJ表演,但我們的應用需要組織多種表演,包括舞者、獨奏或獨唱等。透過研究嘗試重用現有程式碼,我們發現了一個開源函式庫,它為我們提供了兩個有趣的類:MusicianDancer。在Musician類中,主要動作由play方法執行。在Dancer類中,主要動作由dance方法執行。

實作介面卡

為了示範這兩個類是外來的,我們將它們放在一個單獨的模組中。Musician類的程式碼如下:

class Musician:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'音樂家 {self.name}'

    def play(self):
        return '演奏音樂'

然後,Dancer類被定義如下:

class Dancer:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'舞者 {self.name}'

    def dance(self):
        return '跳舞'

現在,我們需要建立一個介面卡,使得MusicianDancer類能夠與Club類合作。介面卡類需要實作organize_event方法,以便俱樂部能夠組織活動。介面卡的程式碼如下:

class Adapter:
    def __init__(self, musician):
        self.musician = musician

    def organize_event(self):
        return self.musician.play()

使用介面卡

現在,我們可以使用介面卡來使MusicianDancer類能夠與Club類合作。程式碼如下:

club = Club('My Club')
musician = Musician('John')
dancer = Dancer('Jane')

adapter = Adapter(musician)
print(club.organize_event())  # 印刷:聘請藝人為人們表演
print(adapter.organize_event())  # 印刷:演奏音樂

adapter = Adapter(dancer)
print(adapter.organize_event())  # 印刷:跳舞

這樣,我們就實作了介面卡模式,讓不相容的介面相容。介面卡模式是一種強大的工具,能夠幫助我們解決軟體設計中的介面不相容問題。

介面介面卡(Adapter)模式

在軟體設計中,當我們需要將不同介面的物件整合在一起時,可能會遇到一些挑戰。介面介面卡(Adapter)模式是一種設計模式,它可以讓我們將一個介面的物件轉換成另一個介面的物件,從而使得不同介面的物件可以一起工作。

問題描述

假設我們有兩個類別:MusicianDancer,它們分別有 play()dance() 方法。然而,客戶端程式碼只知道如何呼叫 organize_performance() 方法(在 Club 類別中),而不知道 play()dance() 方法的存在。

解決方案

為瞭解決這個問題,我們可以使用介面介面卡(Adapter)模式。首先,我們需要建立一個通用的介面卡類別 Adapter,它可以將不同介面的物件轉換成統一的介面。

class Adapter:
    def __init__(self, obj, adapted_methods):
        self.obj = obj
        self.__dict__.update(adapted_methods)

    def __str__(self):
        return str(self.obj)

接下來,我們需要將不相容的物件適配成統一的介面。例如,我們可以將 MusicianDancer 物件適配成 Club 類別的介面。

musician = Musician('Roy Ayers')
dancer = Dancer('John Smith')

musician_adapter = Adapter(musician, {'perform': musician.play})
dancer_adapter = Adapter(dancer, {'perform': dancer.dance})

現在,客戶端程式碼可以使用統一的介面來呼叫 perform() 方法,無需知道不同物件的具體介面。

def main():
    objects = [Club('Jazz Cafe'), musician_adapter, dancer_adapter]

    for obj in objects:
        print(obj.perform())

if __name__ == '__main__':
    main()

結果

使用介面介面卡(Adapter)模式,我們可以將不同介面的物件整合在一起,無需修改客戶端程式碼。這使得我們的程式碼更加靈活和可擴充套件。

圖表翻譯:

  classDiagram
    class Club {
        +organize_performance()
    }
    class Musician {
        +play()
    }
    class Dancer {
        +dance()
    }
    class Adapter {
        +__init__(obj, adapted_methods)
        +__str__()
    }
    Club ..> Adapter
    Musician ..> Adapter
    Dancer ..> Adapter
    Adapter ..> Club
    Adapter ..> Musician
    Adapter ..> Dancer

在這個圖表中,我們可以看到 ClubMusicianDancer 類別如何使用 Adapter 類別來整合在一起。

設計模式的靈活運用是軟體工程走向成熟的重要標誌。深入剖析原型模式、單例模式和介面卡模式的實作細節,我們可以發現,這些模式並非僅僅是程式碼層面的技巧,更體現了軟體設計的核心理念:解耦、重用和擴充套件。

多維比較分析顯示,原型模式透過複製現有物件來建立新物件,有效降低了物件建立的成本,尤其適用於複雜物件的建立場景。然而,深層複製的效能開銷不容忽視,需要審慎評估其適用性。單例模式則確保一個類別只有一個例項,並提供全域存取點,適用於管理分享資源和全域狀態,但過度使用可能增加程式碼的耦合度,降低可測試性。介面卡模式則扮演了橋樑的角色,讓介面不相容的類別能夠協同工作,提升了程式碼的重用性和靈活性,但額外的適配層也可能引入效能損耗。

技術演進預測顯示,隨著軟體系統日趨複雜,設計模式的重要性將更加凸顯。未來,設計模式將與更高階的程式設計正規化和架構模式深度融合,例如與函式式程式設計、回應式程式設計的結合,以及在微服務架構下的應用。預見這些趨勢,並將其融入實務開發,將是技術團隊提升軟體品質和效率的關鍵。

玄貓認為,深入理解並靈活運用設計模式,而非生搬硬套,才能真正發揮其價值。開發者應著重於理解模式背後的設計原則,並根據具體場景選擇最合適的模式,才能打造出更優雅、更健壯的軟體系統。