代理模式提供了一個中介物件來控制對原始物件的存取,實作延遲初始化、存取控制和遠端服務呼叫等功能。延遲初始化將物件的建立延遲到實際使用時,提升效能並減少資源浪費。保護代理控制對敏感物件的存取,確保只有授權使用者才能操作。遠端代理隱藏遠端服務的複雜性,簡化客戶端存取。文章中的程式碼範例清晰地展示了這三種代理模式的 Python 實作方式,並用流程圖和序列圖輔助說明,幫助讀者理解代理模式的運作機制和優勢。此外,文章也提到了責任鏈模式,並以 ATM 的運作方式為例,說明其在處理請求時的應用。

代理模式(Proxy Pattern)深度解析與應使用案例項

代理模式是一種結構型設計模式,旨在為其他物件提供一個代理或佔位符,以控制對該物件的存取。在本篇文章中,我們將深入探討代理模式的三種實作方式:延遲初始化、保護代理和遠端代理,並提供詳細的程式碼範例和實作解析。

延遲初始化(Lazy Initialization)代理

延遲初始化是一種最佳化技術,透過將物件的建立或初始化延遲到實際需要使用時才進行。這種方法可以提高系統效能,減少不必要的資源浪費。

程式碼範例:延遲初始化代理

class LazyProperty:
    def __init__(self, method):
        self.method = method
        self.method_name = method.__name__
        self.value_name = f"_{method.__name__}"

    def __get__(self, instance, owner):
        if instance is None:
            return self
        if not hasattr(instance, self.value_name):
            setattr(instance, self.value_name, self.method(instance))
        return getattr(instance, self.value_name)

class Test:
    def __init__(self):
        self.x = "foo"
        self.y = "bar"
        self._resource = None

    @LazyProperty
    def resource(self):
        print("初始化 self._resource...")
        print(f"... 目前狀態: {self._resource}")
        self._resource = tuple(range(5))
        return self._resource

def main():
    t = Test()
    print(t.x)
    print(t.y)
    print(t.resource)
    print(t.resource)

if __name__ == "__main__":
    main()

內容解密:

此範例展示瞭如何使用LazyProperty類別實作延遲初始化。Test類別中的resource屬性被LazyProperty裝飾,當第一次存取resource時才會執行初始化操作。輸出結果表明,resource只會被初始化一次,第二次存取時直接傳回已初始化的值。

圖表翻譯:

此圖示展示了延遲初始化的流程。當存取代理物件的屬性時,系統會先檢查該屬性是否已經初始化。如果未初始化,則執行初始化操作;如果已初始化,則直接傳回該屬性的值。這種機制有效避免了重複初始化,提高了系統效能。

保護代理(Protection Proxy)實作

保護代理用於控制對敏感物件的存取,確保只有授權的使用者才能存取或修改該物件。

程式碼範例:保護代理

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

    def read(self):
        nb = len(self.users)
        print(f"目前有 {nb} 位使用者: {' '.join(self.users)}")

    def add(self, user):
        self.users.append(user)
        print(f"已新增使用者 {user}")

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

    def read(self):
        self.protected.read()

    def add(self, user):
        sec = input("請輸入密碼: ")
        if sec == self.secret:
            self.protected.add(user)
        else:
            print("密碼錯誤!")

def main():
    info = Info()
    while True:
        print("1. 檢視使用者列表 | 2. 新增使用者 | 3. 離開")
        key = input("請選擇操作: ")
        if key == "1":
            info.read()
        elif key == "2":
            name = input("請輸入使用者名稱: ")
            info.add(name)
        elif key == "3":
            break
        else:
            print(f"未知操作: {key}")

if __name__ == "__main__":
    main()

內容解密:

此範例展示瞭如何使用保護代理控制對敏感資料的存取。Info類別作為保護代理,限制了對SensitiveInfo類別中資料的存取。只有當使用者輸入正確的密碼時,才能新增新的使用者。

圖表翻譯:

此圖示展示了保護代理的工作流程。當使用者選擇操作時,系統會根據不同的操作執行不同的邏輯。如果選擇檢視使用者列表,則直接顯示;如果選擇新增使用者,則需要輸入密碼驗證。驗證成功後,才能新增使用者。

遠端代理(Remote Proxy)實作

遠端代理用於隱藏遠端服務的複雜性,使得客戶端可以像存取本地物件一樣存取遠端服務。

程式碼範例:遠端代理

import xmlrpc.client

class RemoteServiceInterface:
    def read_file(self, filename):
        raise NotImplementedError

    def write_file(self, filename, content):
        raise NotImplementedError

class RemoteService(RemoteServiceInterface):
    def __init__(self, server_url):
        self.server = xmlrpc.client.ServerProxy(server_url)

    def read_file(self, filename):
        return self.server.read_file(filename)

    def write_file(self, filename, content):
        return self.server.write_file(filename, content)

def main():
    server_url = "http://localhost:8080"
    remote_service = RemoteService(server_url)
    print(remote_service.read_file("example.txt"))
    remote_service.write_file("example.txt", "Hello, World!")

if __name__ == "__main__":
    main()

內容解密:

此範例展示瞭如何使用遠端代理存取遠端服務。RemoteService類別作為遠端代理,封裝了與遠端伺服器的通訊細節,使得客戶端可以像呼叫本地方法一樣呼叫遠端服務。

圖表翻譯:

此圖示展示了遠端代理的互動流程。客戶端透過遠端代理存取遠端服務,遠端代理負責將客戶端的請求轉發給遠端服務,並將遠端服務的回應傳回給客戶端。這種機制使得客戶端無需關心遠端通訊的細節。

結構設計模式的應用:代理模式詳解

在軟體開發中,結構設計模式扮演著至關重要的角色,它們幫助開發者建立出乾淨、可維護且可擴充套件的程式碼。代理模式(Proxy Pattern)是其中一種重要的結構設計模式,它允許開發者在不改變原始物件的情況下,對其進行控制和管理的同時,還能提供額外的功能。

代理模式的基本概念

代理模式是一種結構設計模式,它提供了一個替身或代理物件來控制對原始物件的存取。代理物件在客戶端和實際服務之間起到中介作用,能夠在不修改原始服務的情況下,新增額外的功能,如日誌記錄、存取控制或快取等。

實作代理模式:遠端代理範例

首先,我們定義了一個RemoteServiceInterface介面,用於規範遠端服務的行為:

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

接著,我們實作了RemoteService類別,它代表了實際的遠端服務:

class RemoteService(RemoteServiceInterface):
    def read_file(self, file_name):
        # 實際的檔案讀取實作
        return "從遠端伺服器讀取檔案"

    def write_file(self, file_name, contents):
        # 實際的檔案寫入實作
        return "向遠端伺服器寫入檔案"

    def delete_file(self, file_name):
        # 實際的檔案刪除實作
        return "從遠端伺服器刪除檔案"

然後,我們定義了ProxyService類別,它實作了RemoteServiceInterface介面,並作為RemoteService的代理:

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

    def read_file(self, file_name):
        print("代理:轉發讀取請求至RemoteService")
        return self.remote_service.read_file(file_name)

    def write_file(self, file_name, contents):
        print("代理:轉發寫入請求至RemoteService")
        return self.remote_service.write_file(file_name, contents)

    def delete_file(self, file_name):
        print("代理:轉發刪除請求至RemoteService")
        return self.remote_service.delete_file(file_name)

客戶端可以透過ProxyService來與遠端服務互動,而無需直接接觸RemoteService。這樣,代理模式使得遠端服務的呼叫更加透明,並允許在代理層新增額外的邏輯。

代理模式的另一範例:智慧代理

智慧代理是代理模式的另一種應用,它用於管理對分享資源的存取,例如資料函式庫連線。在這個範例中,我們定義了DBConnectionInterface介面和DBConnection類別來代表資料函式庫連線:

from typing import Protocol

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

class DBConnection:
    def __init__(self):
        print("資料函式庫連線已建立")

    def exec_query(self, query):
        return f"執行查詢:{query}"

    def close(self):
        print("資料函式庫連線已關閉")

接著,我們實作了SmartProxy類別,它同樣實作了DBConnectionInterface介面,並負責管理對DBConnection的存取和參考計數:

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"資料函式庫連線目前有 {self.ref_count} 個參考。")

    def exec_query(self, query):
        if self.cnx is None:
            self.access_resource()
        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("參考已釋放...")
            print(f"剩餘參考數:{self.ref_count}")
        if self.ref_count == 0 and self.cnx is not None:
            self.cnx.close()
            self.cnx = None

這個智慧代理確保了資料函式庫連線在需要時才被建立,並且在不再被使用時正確地關閉,從而有效地管理資源。

圖表翻譯:

此圖示展示了代理模式的基本架構。客戶端向代理服務發出請求,代理服務再將請求轉發給實際服務。實際服務處理請求後,將結果傳回給代理服務,最後由代理服務將結果傳回給客戶端。這種架構使得代理服務能夠在不影響客戶端和實際服務的情況下,新增額外的邏輯或控制。

行為設計模式

在前一章中,我們討論了結構設計模式和物件導向程式設計(OOP)模式,這些模式幫助我們建立乾淨、可維護和可擴充套件的程式碼。下一類別設計模式是行為設計模式。行為模式處理物件之間的互聯和演算法。

在本章中,我們將涵蓋以下主要主題:

  • 責任鏈模式
  • 命令模式
  • 觀察者模式
  • 狀態模式
  • 直譯器模式
  • 策略模式
  • 備忘錄模式
  • 迭代器模式
  • 範本模式
  • 其他行為設計模式

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

行為設計模式

技術需求

請參閱第1章中提出的技術需求。本章討論的程式碼的額外技術需求如下:

  • 對於狀態模式部分,請使用以下命令安裝state_machine模組:python -m pip install state_machine
  • 對於直譯器模式部分,請使用以下命令安裝pyparsing模組:python -m pip install pyparsing
  • 對於範本模式部分,請使用以下命令安裝cowpy模組:python -m pip install cowpy

責任鏈模式

責任鏈模式提供了一種優雅的方式來處理請求。每個處理程式在鏈中具有自主權,可以決定是否能夠處理請求或將其委派給鏈中的下一個處理程式。當處理涉及多個處理程式但不一定需要所有處理程式參與的操作時,此模式特別有用。

實際上,這種模式鼓勵我們專注於物件和應用程式中請求的流程。值得注意的是,客戶端程式碼仍然不知道整個處理程式鏈。相反,它只與鏈中的第一個處理元素互動。同樣,每個處理元素只知道其直接後繼者,形成類別似於單連結串列的單向關係。

這種結構的設計目的是實作傳送者(客戶端)和接收者(處理元素)之間的解耦。

真實世界的例子

自動櫃員機(ATM)和一般接受/傳回紙幣或硬幣的機器(例如,販賣機)都使用責任鏈模式。總是有一個單一的插槽用於所有紙幣或硬幣。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Python 代理模式深度解析與應用

package "資料視覺化流程" {
    package "資料準備" {
        component [資料載入] as load
        component [資料清洗] as clean
        component [資料轉換] as transform
    }

    package "圖表類型" {
        component [折線圖 Line] as line
        component [長條圖 Bar] as bar
        component [散佈圖 Scatter] as scatter
        component [熱力圖 Heatmap] as heatmap
    }

    package "美化輸出" {
        component [樣式設定] as style
        component [標籤註解] as label
        component [匯出儲存] as export
    }
}

load --> clean --> transform
transform --> line
transform --> bar
transform --> scatter
transform --> heatmap
line --> style --> export
bar --> label --> export

note right of scatter
  探索變數關係
  發現異常值
end note

@enduml

圖表翻譯:

此圖示展示了ATM處理提款請求的流程。當使用者請求$175時,系統首先檢查是否有足夠的$100紙幣。如果有,則分配$100紙幣;如果沒有,則檢查$50紙幣。依此類別推,直到分配正確的金額或回報錯誤。這個流程清晰地展示了責任鏈模式在處理請求時的靈活性和解耦特性。

責任鏈模式的應用場景

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

另一個責任鏈模式有用的情況是,當我們知道多個物件可能需要處理單個請求時。在事件驅動的程式設計中,單個事件(如左鍵點選)可以被多個處理程式捕捉。

責任鏈模式的實作

在Python中有許多實作責任鏈模式的方法。其中一種實作方式是使用動態排程,以Pythonic的方式處理請求。

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

 def handle(self, request):
 if self.successor:
 self.successor.handle(request)

class ConcreteHandler1(Handler):
 def handle(self, request):
 if request == 'request1':
 print('ConcreteHandler1 handled the request')
 else:
 super().handle(request)

class ConcreteHandler2(Handler):
 def handle(self, request):
 if request == 'request2':
 print('ConcreteHandler2 handled the request')
 else:
 super().handle(request)

# 建立責任鏈
handler1 = ConcreteHandler1()
handler2 = ConcreteHandler2(handler1)

# 處理請求
handler2.handle('request1')  # 輸出:ConcreteHandler1 handled the request
handler2.handle('request2')  # 輸出:ConcreteHandler2 handled the request

內容解密:

此程式碼展示了責任鏈模式的基本實作。Handler類別定義了處理請求的基本結構,並包含一個指向下一個處理程式的參照。ConcreteHandler1ConcreteHandler2是具體的處理程式類別,它們根據請求型別進行處理。如果當前處理程式無法處理請求,則將請求傳遞給鏈中的下一個處理程式。這種設計允許靈活地新增或移除處理程式,並且客戶端程式碼無需瞭解整個處理鏈的結構。