Python-Proxy-Muster

Das Proxy-Muster ist ein strukturelles Entwurfsmuster, das einen Ersatz oder Platzhalter für ein anderes Objekt bereitstellt, um den Zugriff darauf zu steuern. Einfach ausgedrückt ist ein Proxy ein Vermittler, der zwischen dem Client und dem Zielobjekt agiert.

Proxy-Metaphern aus dem echten Leben

Stellen Sie sich ein Szenario aus dem echten Leben vor:

  • Immobilienmakler: Sie müssen nicht direkt mit dem Hausbesitzer kommunizieren; Sie können über den Makler Informationen zu Immobilienangeboten erhalten und Besichtigungen vereinbaren.
  • Promi-Agent: Wenn ein Unternehmen einen Prominenten für ein Endorsement einladen möchte, muss es die Details der Zusammenarbeit über den Agenten aushandeln.
  • Netzwerk-Proxy-Server: Ihre Netzwerkanfrage läuft zuerst über den Proxy-Server, und dann leitet der Proxy-Server sie an die Zielwebsite weiter.

In diesen Beispielen ist der Vermittler/Agent der Proxy, und er kontrolliert den Zugriff auf das reale Objekt.


Kernkomponenten des Proxy-Musters

Das Proxy-Muster umfasst typischerweise drei Schlüsselrollen:

1. Abstraktes Subjekt (Subject)

Definiert eine gemeinsame Schnittstelle für das reale Subjekt und das Proxy-Subjekt, sodass das Proxy-Subjekt überall dort verwendet werden kann, wo das reale Subjekt verwendet wird.

2. Reales Subjekt

Definiert das reale Objekt, das der Proxy repräsentiert; es ist das Objekt, das letztendlich referenziert wird.

3. Proxy

Hält eine Referenz, die es dem Proxy ermöglicht, auf die Entität zuzugreifen, stellt eine Schnittstelle bereit, die mit dem realen Subjekt identisch ist, und kontrolliert den Zugriff auf das reale Subjekt.


Arten des Proxy-Musters

Das Proxy-Muster hat mehrere Varianten, jede mit unterschiedlichen Anwendungsfällen:

1. Virtueller Proxy

Verzögert die Erstellung teurer Objekte, bis sie tatsächlich benötigt werden.

2. Schutz-Proxy

Steuert den Zugriff auf das ursprüngliche Objekt und wird verwendet, wenn das Objekt unterschiedliche Zugriffsberechtigungen haben soll.

3. Remote-Proxy

Stellt einen lokalen Vertreter für ein Objekt bereit, das sich in einem anderen Adressraum befindet.

4. Intelligenter Referenz-Proxy

Führt zusätzliche Operationen beim Zugriff auf das Objekt aus, wie Referenzzählung, Lazy Loading usw.


Implementierung des Proxy-Musters in Python

Lassen Sie uns die Implementierung des Proxy-Musters anhand konkreter Codebeispiele verstehen.

Grundlegende Schnittstellendefinition

Zuerst definieren wir die abstrakte Subjekt-Schnittstelle:

Beispiel

from abc import ABC, abstractmethod
from typing import Any

class Subject(ABC):
    """
Abstrakte Subjekt-Schnittstelle, die die gemeinsamen Operationen des realen Subjekts und des Proxys definiert
    """

   
    @abstractmethod
    def request(self) -> Any:
        """Hauptmethode zum Ausführen einer Anfrage"""
        pass

Implementierung des realen Subjekts

Beispiel

class RealSubject(Subject):
    """
Klasse des realen Subjekts, die die zentrale Geschäftslogik enthält
Normalerweise teuer zu erstellen und zu initialisieren
    """

   
    def __init__(self, name: str):
        self.name = name
        print(f"Erzeuge RealSubject-Instanz: {self.name} (dies ist eine teure Operation)")
   
    def request(self) -> str:
        """Führe die echte Anfrage aus"""
        print(f"RealSubject: Bearbeite Anfrage - {self.name}")
        return f"Antwort von {self.name}"

Implementierung der Proxy-Klasse

Beispiel

class Proxy(Subject):
    """
Proxy, der den Zugriff auf das echte Subjekt kontrolliert
Zusätzliche Funktionen können hinzugefügt werden, wie z. B. Lazy Loading, Zugriffskontrolle usw.
    """

   
    def __init__(self, subject_name: str):
        self.subject_name = subject_name
        self._real_subject = None  # Verzögerte Initialisierung
   
    def _lazy_init(self) -> None:
        """Verzögertes Laden des echten Subjekts"""
        if self._real_subject is None:
            self._real_subject = RealSubject(self.subject_name)
   
    def request(self) -> str:
        """Request-Methode des Proxys, bei der zusätzliche Logik hinzugefügt werden kann"""
        print("Proxy: Vorverarbeitung vor dem Aufruf des realen Objekts")
       
        # Das reale Objekt verzögert laden
        self._lazy_init()
       
        # Methode des realen Objekts aufrufen
        result = self._real_subject.request()
       
        print("Proxy: Nachverarbeitung nach dem Aufruf des realen Objekts")
        return f"Verbessertes Ergebnis vom Proxy: {result}"

Client-Code

Beispiel

def client_code(subject: Subject) -> None:
    """
Client-Code, der über die abstrakte Schnittstelle mit dem Subjekt interagiert
Es weiß nicht und kümmert sich nicht darum, ob es das reale Subjekt oder den Proxy verwendet
    """

    print("Client: Operation wird gestartet")
    result = subject.request()
    print(f"Client: Ergebnis empfangen - {result}")
    print()

# Verwendungsbeispiel
if __name__ == "__main__":
    print("Das reale Subjekt direkt verwenden:")
    real_subject = RealSubject("Reales Objekt A")
    client_code(real_subject)
   
    print("Den Proxy verwenden:")
    proxy = Proxy("Proxy-Objekt B")
    client_code(proxy)
   
    # Denselben Proxy erneut verwenden, um den Lazy-Loading-Effekt zu beobachten
    print("Denselben Proxy erneut verwenden:")
    client_code(proxy)

Wenn Sie den obigen Code ausführen, sehen Sie die folgende Ausgabe:

直接使用真实主题:
创建 RealSubject 实例: 真实对象A (这是一个昂贵的操作)
客户端: 开始执行操作
RealSubject: 处理请求 - 真实对象A
客户端: 收到结果 - 来自 真实对象A 的响应

使用代理:
客户端: 开始执行操作
Proxy: 在调用真实对象前进行预处理
创建 RealSubject 实例: 代理对象B (这是一个昂贵的操作)
RealSubject: 处理请求 - 代理对象B
Proxy: 在调用真实对象后进行后处理
客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应

再次使用同一个代理:
客户端: 开始执行操作
Proxy: 在调用真实对象前进行预处理
RealSubject: 处理请求 - 代理对象B
Proxy: 在调用真实对象后进行后处理
客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应

Praxisbeispiel: Proxy zum Laden von Bildern

Betrachten wir ein praktischeres Beispiel – einen virtuellen Proxy für das Laden von Bildern.

Bildladesystem

Beispiel

from pathlib import Path
import time

class Image:
    """Echte Bildklasse, die den teuren Vorgang des Ladens eines großen Bildes simuliert"""
   
    def __init__(self, filename: str):
        self.filename = filename
        self._load_image()
   
    def _load_image(self) -> None:
        """Simuliere den teuren Vorgang des Ladens eines großen Bildes"""
        print(f"Bild wird geladen: {self.filename} (dies kann ein paar Sekunden dauern...)")
        time.sleep(2)  # Ladezeit simulieren
        print(f"Bild {self.filename} erfolgreich geladen!")
   
    def display(self) -> None:
        """Das Bild anzeigen"""
        print(f"Bild wird angezeigt: {self.filename}")

class ImageProxy:
    """Bild-Proxy, der Lazy Loading implementiert"""
   
    def __init__(self, filename: str):
        self.filename = filename
        self._image = None
   
    def display(self) -> None:
        """Das Bild anzeigen, wobei es bei Bedarf zuerst geladen wird"""
        if self._image is None:
            print("Proxy: erkannt, dass das Bild nicht geladen ist; Lazy Loading wird gestartet...")
            self._image = Image(self.filename)
        else:
            print("Proxy: Bild ist bereits geladen, wird direkt angezeigt")
       
        self._image.display()

# Verwendungsbeispiel
def demo_image_proxy():
    print("Erstellen eines Bild-Proxys (das Bild wird nicht sofort geladen)")
    proxy = ImageProxy("large_photo.jpg")
   
    print("\n"Erster Aufruf von display() - löst Lazy Loading aus:")
    proxy.display()
   
    print("\n"Zweiter Aufruf von display() - verwendet direkt das bereits geladene Bild:")
    proxy.display()

# Beispiel ausführen
demo_image_proxy()

Beispiel für Schutzproxy: Zugriffskontrolle

Schutzproxys werden verwendet, um den Zugriff auf sensible Ressourcen zu kontrollieren.

Beispiel

class SensitiveData:
    """Klasse für sensible Daten"""
   
    def __init__(self, data: str):
        self.data = data
   
    def read_data(self) -> str:
        """Sensible Daten lesen"""
        return f"Sensible Daten: {self.data}"

class ProtectionProxy:
    """Schutzproxy, der Zugriffskontrolle implementiert"""
   
    def __init__(self, sensitive_data: SensitiveData, user_role: str):
        self._sensitive_data = sensitive_data
        self.user_role = user_role
   
    def read_data(self) -> str:
        """Daten lesen, aber Berechtigungsprüfung durchführen"""
        if self.user_role != "admin":
            return "Fehler: Unzureichende Berechtigungen, nur Administratoren können auf sensible Daten zugreifen"
       
        return self._sensitive_data.read_data()

# Verwendungsbeispiel
def demo_protection_proxy():
    sensitive_data = SensitiveData("Vertrauliche Informationen: Projektbudget beträgt 1 Million Yuan")
   
    # Ein regulärer Benutzer versucht zuzugreifen
    user_proxy = ProtectionProxy(sensitive_data, "user")
    print("Regulärer Benutzer versucht zuzugreifen:")
    print(user_proxy.read_data())
   
    # Administrator greift zu
    admin_proxy = ProtectionProxy(sensitive_data, "admin")
    print("\nAdministratorzugriff:)
    print(admin_proxy.read_data())

demo_protection_proxy()

Vor- und Nachteile des Proxy-Musters

Vorteile

  1. Objektzugriff steuern: Der Proxy kann steuern, wie und wann der Client auf das echte Objekt zugreift
  2. Lazy-Loading-Optimierung: Virtuelle Proxys können die Erstellung teurer Objekte verzögern und die Leistung verbessern
  3. Erhöhte Sicherheit: Der Schutz-Proxy kann Zugriffskontrolllogik hinzufügen
  4. Open/Closed-Prinzip: Neue Proxys können eingeführt werden, ohne den Client-Code zu ändern
  5. Trennung von Belangen: Der Proxy kann Hilfsfunktionen übernehmen, die nicht mit der Kern-Geschäftslogik zusammenhängen

Nachteile

  1. Erhöhte Komplexität: Die Einführung einer neuen Abstraktionsebene macht die Codestruktur komplexer
  2. Antwortlatenz: Der Proxy kann die Bearbeitungszeit von Anfragen erhöhen
  3. Möglichkeit von Over-Engineering: Für einfache Szenarien mag die Verwendung eines Proxys unnötig erscheinen

Anwendungsszenarien des Proxy-Musters

Das Proxy-Muster ist besonders nützlich in den folgenden Szenarien:

1. Lazy Loading

Wenn die Erstellung eines Objekts teuer ist, das Objekt aber möglicherweise nicht sofort verwendet wird.

2. Zugriffskontrolle

Wenn Zugriffsrechte auf bestimmte Objekte eingeschränkt werden müssen.

3. Lokaler Stellvertreter (Remote-Repräsentation)

Bereitstellen einer lokalen Schnittstelle für entfernte Objekte, z. B. RPC-Aufrufe.

4. Protokollierung

Hinzufügen von Protokollierung vor und nach Methodenaufrufen.

5. Caching

Bereitstellen von Caching für die Ergebnisse aufwändiger Operationen.


Praktische Übungen

Um Ihr Verständnis des Proxy-Musters zu vertiefen, versuchen Sie, die folgenden Übungen abzuschließen:

Übung 1: Cache-Proxy

Erstellen Sie einen Cache-Proxy, der einer Funktion, die die Fibonacci-Folge berechnet, Caching-Funktionalität hinzufügt:

Beispiel

class FibonacciCalculator:
    """Berechnet die Fibonacci-Folge"""
   
    def fibonacci(self, n: int) -> int:
        if n <= 1:
            return n
        return self.fibonacci(n - 1) + self.fibonacci(n - 2)

# Deine Aufgabe: Implementiere die Klasse FibonacciCacheProxy
# Anforderung: Füge Caching zur Fibonacci-Berechnung hinzu, um wiederholte Berechnung zu vermeiden

class FibonacciCacheProxy:
    def __init__(self):
        self._calculator = FibonacciCalculator()
        self._cache = {}  # Implementiere die Caching-Logik hier
   
    def fibonacci(self, n: int) -> int:
        # Implementiere Caching-Logik
        pass

# Testcode
def test_cache_proxy():
    proxy = FibonacciCacheProxy()
   
    print("Erste Berechnung von fib(10):")
    result1 = proxy.fibonacci(10)
    print(f"Ergebnis: {result1}")
   
    print("Zweite Berechnung von fib(10) (sollte aus dem Cache abgerufen werden):")
    result2 = proxy.fibonacci(10)
    print(f"Ergebnis: {result2}")
   
    print(f"Beide Ergebnisse sind gleich: {result1 == result2}")

Übung 2: Logging-Proxy

Erstelle einen Logging-Proxy, der Informationen zu Methodenaufrufen aufzeichnet:

Beispiel

class DatabaseService:
    """Datenbankdienst"""
   
    def query(self, sql: str) -> str:
        return f"Abfrage wird ausgeführt: {sql}"
   
    def update(self, sql: str) -> str:
        return f"Update wird ausgeführt: {sql}"

# Deine Aufgabe: Implementiere die LoggingProxy-Klasse
# Anforderung: Aufrufzeit, Parameter und Ergebnis jeder Methode aufzeichnen

class LoggingProxy:
    def __init__(self):
        self._service = DatabaseService()
   
    def query(self, sql: str) -> str:
        # Logging-Logik hinzufügen
        pass
   
    def update(self, sql: str) -> str:
        # Logging-Logik hinzufügen
        pass

Zusammenfassung

Das Proxy-Muster ist ein leistungsfähiges Entwurfsmuster, das den Zugriff auf reale Objekte durch Einführung einer Zwischenschicht steuert. Die Implementierung des Proxy-Musters in Python ist relativ einfach, vor allem dank der dynamischen Eigenschaften von Python.

Kernpunkte

  1. Der Proxy ist ein Vermittler: Fungiert als Vermittler zwischen dem Client und dem realen Objekt
  2. Konsistente Schnittstelle: Der Proxy und das reale Objekt implementieren dieselbe Schnittstelle
  3. Zugriffskontrolle: Der Proxy kann zusätzliche Steuerlogik hinzufügen, wie z. B. Lazy Loading, Berechtigungsprüfungen usw.
  4. Flexible Anwendung: Je nach Bedarf verschiedene Proxy-Typen wählen
Weitere Erweiterungen