代理模式提供了一種間接存取物件的方式,有助於提升程式碼的彈性與可維護性。本文除了介紹代理模式的三種常見型別:保護代理、遠端代理和智慧代理,更進一步探討了它們在實際應用中的程式碼實作細節,包含介面定義、許可權控管、遠端呼叫轉發以及資源分享管理等關鍵技術。透過這些例項,開發者能更清晰地理解代理模式的運作機制,並學習如何將其應用於各種不同的軟體開發場景。

from abc import ABC, abstractmethod

class Subject(ABC):
    @abstractmethod
    def request(self):
        pass

class RealSubject(Subject):
    def request(self):
        print("RealSubject: Handling request.")

class Proxy(Subject):
    def __init__(self, real_subject):
        self._real_subject = real_subject

    def request(self):
        print("Proxy: Checking access prior to firing a real request.")
        self._real_subject.request()

if __name__ == "__main__":
    real_subject = RealSubject()
    proxy = Proxy(real_subject)
    proxy.request()

代理模式(Proxy Pattern)詳解與實作

代理模式是一種結構型設計模式,允許透過代理物件控制對真實物件的存取。本文將探討代理模式的三種主要實作方式:保護代理、遠端代理和智慧代理,並提供完整的程式碼範例。

保護代理實作

保護代理的主要目的是控制對敏感資源的存取。以下是一個範例,展示如何使用保護代理來管理使用者資訊:

from abc import ABC, abstractmethod

class SensitiveInfoInterface(ABC):
    @abstractmethod
    def read_list(self):
        pass

    @abstractmethod
    def add_user(self, username, secret):
        pass

class SensitiveInfo(SensitiveInfoInterface):
    def __init__(self):
        self.users = ['nick', 'tom', 'ben', 'mike']

    def read_list(self):
        return f"There are {len(self.users)} users: {' '.join(self.users)}"

    def add_user(self, username, secret):
        if secret == '0xdeadbeef':
            self.users.append(username)
            return f"Added user {username}"
        return "Invalid secret"

class Info:
    def __init__(self):
        self.protected = SensitiveInfo()
        self.secret = '0xdeadbeef'

    def read_list(self):
        return self.protected.read_list()

    def add_user(self, username):
        return self.protected.add_user(username, self.secret)

def main():
    info = Info()
    while True:
        print("1. read list |==| 2. add user |==| 3. quit")
        choice = input("choose option: ")
        if choice == '1':
            print(info.read_list())
        elif choice == '2':
            username = input("choose username: ")
            print(info.add_user(username))
        elif choice == '3':
            break
        else:
            print("Invalid choice")

if __name__ == "__main__":
    main()

內容解密:

  1. SensitiveInfoInterface定義了存取敏感資訊的操作介面。
  2. SensitiveInfo類別實作了SensitiveInfoInterface,並管理使用者清單。
  3. Info類別作為保護代理,控制對SensitiveInfo的存取,並驗證新增使用者的秘密訊息。
  4. main函式展示瞭如何使用Info類別進行互動式操作。

遠端代理實作

遠端代理用於隱藏遠端服務的複雜性。以下是一個範例,展示如何使用遠端代理來管理遠端檔案操作:

from abc import ABC, abstractmethod

class RemoteServiceInterface(ABC):
    @abstractmethod
    def read_file(self, file_name):
        pass

    @abstractmethod
    def write_file(self, file_name, contents):
        pass

    @abstractmethod
    def delete_file(self, file_name):
        pass

class RemoteService(RemoteServiceInterface):
    def read_file(self, file_name):
        # 實際的讀取檔案實作
        return f"Reading file {file_name} from remote server"

    def write_file(self, file_name, contents):
        # 實際的寫入檔案實作
        return f"Writing to file {file_name} on remote server"

    def delete_file(self, file_name):
        # 實際的刪除檔案實作
        return f"Deleting file {file_name} from remote server"

class ProxyService(RemoteServiceInterface):
    def __init__(self):
        self.remote_service = RemoteService()

    def read_file(self, file_name):
        print("Proxy: Forwarding read request to RemoteService")
        return self.remote_service.read_file(file_name)

    def write_file(self, file_name, contents):
        print("Proxy: Forwarding write request to RemoteService")
        return self.remote_service.write_file(file_name, contents)

    def delete_file(self, file_name):
        print("Proxy: Forwarding delete request to RemoteService")
        return self.remote_service.delete_file(file_name)

def main():
    proxy = ProxyService()
    print(proxy.read_file("example.txt"))

if __name__ == "__main__":
    main()

內容解密:

  1. RemoteServiceInterface定義了遠端服務的操作介面。
  2. RemoteService類別實作了RemoteServiceInterface,提供實際的遠端檔案操作。
  3. ProxyService類別作為遠端代理,轉發客戶端的請求到RemoteService
  4. main函式展示瞭如何使用ProxyService進行檔案操作。

智慧代理實作

智慧代理用於管理對分享資源的存取。以下是一個範例,展示如何使用智慧代理來管理資料函式庫連線:

from typing import Protocol

class DBConnectionInterface(Protocol):
    def exec_query(self, query):
        ...

class DBConnection:
    def __init__(self):
        print("DB connection created")

    def exec_query(self, query):
        return f"Executing query: {query}"

    def close(self):
        print("DB connection closed")

class SmartProxy:
    def __init__(self):
        self.cnx = None
        self.ref_count = 0

    def access_resource(self):
        if self.cnx is None:
            self.cnx = DBConnection()
        self.ref_count += 1
        print(f"DB connection now has {self.ref_count} references.")

    def exec_query(self, query):
        if self.cnx is None:
            self.access_resource()
        return self.cnx.exec_query(query)

    def release(self):
        self.ref_count -= 1
        print(f"DB connection now has {self.ref_count} references.")
        if self.ref_count == 0:
            self.cnx.close()
            self.cnx = None

def main():
    proxy = SmartProxy()
    proxy.exec_query("SELECT * FROM users")
    proxy.exec_query("INSERT INTO users VALUES ('new_user')")
    proxy.release()

if __name__ == "__main__":
    main()

內容解密:

  1. DBConnectionInterface定義了資料函式庫連線的操作介面。
  2. DBConnection類別實作了DBConnectionInterface,提供實際的資料函式庫連線操作。
  3. SmartProxy類別作為智慧代理,管理對DBConnection的存取,並追蹤參考計數。
  4. main函式展示瞭如何使用SmartProxy進行資料函式庫操作。

行為設計模式(Behavioral Design Patterns)

在前一章中,我們討論了結構設計模式(Structural Design Patterns)以及物件導向程式設計(OOP)模式,這些模式幫助我們建立乾淨、可維護和可擴充套件的程式碼。接下來,我們將探討行為設計模式(Behavioral Design Patterns),這類別模式主要處理物件之間的互連以及演算法。

本章主要內容

  • 鏈式責任模式(The Chain of Responsibility pattern)
  • 命令模式(The Command pattern)
  • 觀察者模式(The Observer pattern)
  • 狀態模式(The State pattern)
  • 解析器模式(The Interpreter pattern)
  • 策略模式(The Strategy pattern)
  • 備忘錄模式(The Memento pattern)
  • 迭代器模式(The Iterator pattern)
  • 範本模式(The Template pattern)
  • 其他行為設計模式

在本章結束時,您將瞭解如何使用行為模式來改善軟體專案的設計。

代理模式(Proxy Pattern)深度解析

代理模式是一種結構設計模式,它允許我們建立一個代理物件來控制對其他物件的存取。在本文中,我們將探討代理模式的實作細節。

智慧代理(Smart Proxy)實作範例

class DBConnectionInterface:
    def exec_query(self, query):
        pass

class DBConnection(DBConnectionInterface):
    def __init__(self):
        print("DB connection created")

    def exec_query(self, query):
        print(f"Executing query: {query}")
        # 假設這裡執行查詢並傳回結果
        return f"Result of {query}"

    def close(self):
        print("DB connection closed")

class SmartProxy(DBConnectionInterface):
    def __init__(self):
        self.cnx = None
        self.ref_count = 0

    def exec_query(self, query):
        if self.cnx is None:
            self.cnx = DBConnection()
        self.ref_count += 1
        print(f"DB connection now has {self.ref_count} references.")
        result = self.cnx.exec_query(query)
        print(result)
        self.release_resource()
        return result

    def release_resource(self):
        if self.ref_count > 0:
            self.ref_count -= 1
            print("Reference released...")
            print(f"{self.ref_count} remaining refs.")
        if self.ref_count == 0 and self.cnx is not None:
            self.cnx.close()
            self.cnx = None

if __name__ == "__main__":
    proxy = SmartProxy()
    proxy.exec_query("SELECT * FROM users")
    proxy.exec_query("UPDATE users SET name = 'John Doe' WHERE id = 1")

程式碼解析

  1. 我們首先定義了 DBConnectionInterface 介面和實作它的 DBConnection 類別,代表資料函式庫連線。
  2. 然後,我們定義了 SmartProxy 類別,它也實作了 DBConnectionInterface
  3. SmartProxy 中,我們管理了對 DBConnection 的參照計數,並在參照計數為零時關閉資料函式庫連線。

輸出結果

DB connection created
DB connection now has 1 references.
Executing query: SELECT * FROM users
Result of SELECT * FROM users
Reference released...
0 remaining refs.
DB connection closed
DB connection created
DB connection now has 1 references.
Executing query: UPDATE users SET name = 'John Doe' WHERE id = 1
Result of UPDATE users SET name = 'John Doe' WHERE id = 1
Reference released...
0 remaining refs.
DB connection closed

這個範例展示了代理模式如何幫助我們實作對資料函式庫連線的管理,避免資源浪費或連線洩漏。

行為設計模式(Behavioral Design Patterns)中的責任鏈模式(Chain of Responsibility Pattern)

技術需求

在探討責任鏈模式之前,請確保已安裝相關模組:

  • 安裝 state_machine 模組:python -m pip install state_machine
  • 安裝 pyparsing 模組:python -m pip install pyparsing
  • 安裝 cowpy 模組:python -m pip install cowpy

責任鏈模式簡介

責任鏈模式提供了一種優雅的方式來處理請求,透過將請求傳遞給一系列處理器。每個處理器都可以決定是否能夠處理請求,或將其進一步委派給鏈中的下一個處理器。當涉及多個處理器但不一定需要所有處理器都參與的情況下,這種模式尤其有用。

真實世界的例子

自動櫃員機(ATM)和任何接受/傳回鈔票或硬幣的機器(如自動販賣機)都使用了責任鏈模式。總是會有一個單一的插槽用於所有鈔票,如下圖所示: 此圖示

graph LR
    A[使用者] -->|插入卡片|> B[讀卡機]
    B -->|驗證卡片|> C[驗證系統]
    C -->|核准|> D[出鈔模組]
    C -->|拒絕|> E[錯誤處理]
    D -->|出鈔|> F[使用者]
    E -->|顯示錯誤|> F

圖表翻譯: 上圖展示了ATM的基本運作流程,從使用者插入卡片開始,經過讀卡機驗證、驗證系統核准,最後到出鈔或顯示錯誤訊息的過程。

在某些網路框架中,過濾器或中介軟體是在HTTP請求到達目標之前執行的程式碼片段。有一系列過濾器,每個過濾器執行不同的操作(如使用者認證、記錄、日誌壓縮等),並將請求轉發給下一個過濾器,直到鏈結束,或者如果出現錯誤(如認證失敗三次),則中斷流程。

使用案例

使用責任鏈模式,我們為多個不同的物件提供了滿足特定請求的機會。當我們事先不知道哪個物件應該滿足給定的請求時,這非常有用。例如,在採購系統中,有許多審批機構。一個審批機構可能能夠批准一定金額以下的訂單,如果訂單金額超過該值,則將訂單傳送給鏈中的下一個審批機構。

另一個案例是事件驅動程式設計。單一事件(如左鍵點選)可以被多個監聽器捕捉。

責任鏈模式的實作

有多種方式可以在Python中實作責任鏈模式。以下是一個簡單的事件驅動系統的實作範例:

事件類別(Event Class)

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

    def __str__(self):
        return self.name

元件類別(Widget Class)

class Widget:
    def __init__(self, parent=None):
        self.parent = parent

    def handle(self, event):
        handler = f"handle_{event}"
        if hasattr(self, handler):
            method = getattr(self, handler)
            method(event)
        elif self.parent is not None:
            # 如果當前元件無法處理事件,則交由父元件處理
            self.parent.handle(event)

內容解密:

  1. Event類別:定義了一個簡單的事件類別,包含事件名稱。
  2. Widget類別:作為應用程式的核心類別,使用動態排程來決定誰是特定請求(事件)的處理者。如果當前元件無法處理事件,則交由其父元件處理。
  3. 使用動態排程:透過hasattr()getattr()方法檢查並呼叫相應的事件處理方法。

這種設計允許客戶端程式碼只與鏈的第一個處理元素互動,而每個處理元素只知道其直接後繼者,形成了一個單向關係,類別似於單向連結串列。這樣就實作了客戶端和接收者之間的解耦。