Patron Procuration en Python
Le patron Proxy est un patron de conception structurel qui fournit un substitut ou un espace réservé pour un autre objet afin de contrôler l'accès à celui-ci. En termes simples, un proxy est un intermédiaire qui fait le lien entre le client et l'objet cible.
Métaphores du proxy dans la vie réelle
Imaginez un scénario de la vie réelle :
- Agent immobilier: Vous n'avez pas besoin de communiquer directement avec le propriétaire ; vous pouvez découvrir les annonces immobilières et planifier des visites grâce à l'agent.
- Agent de célébrité: Si une entreprise souhaite inviter une célébrité pour un endossement, elle doit négocier les détails de la coopération par l'intermédiaire de l'agent.
- Serveur proxy réseau: Votre requête réseau passe d'abord par le serveur proxy, puis le serveur proxy la transmet au site web cible.
Dans ces exemples, l'intermédiaire/l'agent est le proxy, et il contrôle l'accès à l'objet réel.
Composants principaux du patron Proxy
Le patron Proxy implique généralement trois rôles clés :
1. Sujet abstrait (Subject)
Définit une interface commune pour le sujet réel et le sujet proxy, afin que le sujet proxy puisse être utilisé partout où le sujet réel est utilisé.
2. Sujet réel
Définit l'objet réel que le proxy représente ; c'est l'objet qui sera finalement référencé.
3. Proxy
Détient une référence qui permet au proxy d'accéder à l'entité, fournit une interface identique au sujet réel et contrôle l'accès au sujet réel.

Types du patron Proxy
Le patron Proxy possède plusieurs variantes, chacune avec des cas d'utilisation différents :
1. Proxy virtuel
Retarde la création d'objets coûteux jusqu'à ce qu'ils soient réellement nécessaires.
2. Proxy de protection
Contrôle l'accès à l'objet original, utilisé lorsque l'objet doit avoir différentes autorisations d'accès.
3. Proxy distant
Fournit un représentant local pour un objet situé dans un espace d'adressage différent.
4. Proxy de référence intelligent
Effectue des opérations supplémentaires lors de l'accès à l'objet, telles que le comptage de références, le chargement paresseux, etc.
Implémenter le patron Proxy en Python
Comprenons la mise en œuvre du patron Proxy à travers des exemples de code spécifiques.
Définition de l'interface de base
Tout d'abord, nous définissons l'interface de sujet abstrait :
Exemple
from typing import Any
class Subject(ABC):
"""
Interface de sujet abstrait, définissant les opérations communes du sujet réel et du proxy
"""
@abstractmethod
def request(self) -> Any:
"""Méthode principale pour exécuter une requête"""
pass
Implémentation du sujet réel
Exemple
"""
Classe du sujet réel, contenant la logique métier principale
Généralement coûteux à créer et à initialiser
"""
def __init__(self, name: str):
self.name = name
print(f"Création d'une instance de RealSubject : {self.name} (cette opération est coûteuse)")
def request(self) -> str:
"""Exécute la requête réelle"""
print(f"RealSubject : traitement de la requête - {self.name}")
return f"Réponse de {self.name}"
Implémentation de la classe Proxy
Exemple
"""
Classe Proxy, contrôlant l'accès au sujet réel
Des fonctionnalités supplémentaires peuvent être ajoutées, telles que le chargement paresseux, le contrôle d'accès, etc.
"""
def __init__(self, subject_name: str):
self.subject_name = subject_name
self._real_subject = None # Initialisation paresseuse
def _lazy_init(self) -> None:
"""Charger paresseusement l'objet sujet réel"""
if self._real_subject is None:
self._real_subject = RealSubject(self.subject_name)
def request(self) -> str:
"""Méthode request du Proxy, où une logique supplémentaire peut être ajoutée"""
print("Proxy : prétraitement avant d'appeler l'objet réel")
# Charger l'objet réel de manière paresseuse
self._lazy_init()
# Appeler la méthode de l'objet réel
result = self._real_subject.request()
print("Proxy : post-traitement après l'appel de l'objet réel")
return f"Résultat amélioré du proxy : {result}"
Code client
Exemple
"""
Code client, interagissant avec le sujet via l'interface abstraite
Ne sait pas et ne se soucie pas de savoir s'il utilise le sujet réel ou le proxy
"""
print("Client : démarrage de l'opération")
result = subject.request()
print(f"Client : résultat reçu - {result}")
print()
# Exemple d'utilisation
if __name__ == "__main__":
print("Utilisation directe du sujet réel :")
real_subject = RealSubject("Objet réel A")
client_code(real_subject)
print("Utilisation du proxy :")
proxy = Proxy("Objet proxy B")
client_code(proxy)
# Utilisez à nouveau le même proxy pour observer l'effet de chargement paresseux
print("Utilisation du même proxy à nouveau :")
client_code(proxy)
En exécutant le code ci-dessus, vous verrez la sortie suivante :
直接使用真实主题: 创建 RealSubject 实例: 真实对象A (这是一个昂贵的操作) 客户端: 开始执行操作 RealSubject: 处理请求 - 真实对象A 客户端: 收到结果 - 来自 真实对象A 的响应 使用代理: 客户端: 开始执行操作 Proxy: 在调用真实对象前进行预处理 创建 RealSubject 实例: 代理对象B (这是一个昂贵的操作) RealSubject: 处理请求 - 代理对象B Proxy: 在调用真实对象后进行后处理 客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应 再次使用同一个代理: 客户端: 开始执行操作 Proxy: 在调用真实对象前进行预处理 RealSubject: 处理请求 - 代理对象B Proxy: 在调用真实对象后进行后处理 客户端: 收到结果 - 代理增强的结果: 来自 代理对象B 的响应
Exemple concret : proxy de chargement d'images
Regardons un exemple plus pratique — un proxy virtuel pour le chargement d'images.
Système de chargement d'images
Exemple
import time
class Image:
"""Classe d'image réelle, simulant l'opération coûteuse de chargement d'une grande image"""
def __init__(self, filename: str):
self.filename = filename
self._load_image()
def _load_image(self) -> None:
"""Simuler l'opération coûteuse de chargement d'une grande image"""
print(f"Chargement de l'image : {self.filename} (cela peut prendre quelques secondes...)")
time.sleep(2) # Simuler le temps de chargement
print(f"Image {self.filename} chargée avec succès !")
def display(self) -> None:
"""Afficher l'image"""
print(f"Affichage de l'image : {self.filename}")
class ImageProxy:
"""Proxy d'image, implémentant le chargement différé"""
def __init__(self, filename: str):
self.filename = filename
self._image = None
def display(self) -> None:
"""Afficher l'image, en la chargeant d'abord si nécessaire"""
if self._image is None:
print("Proxy : image non chargée détectée, démarrage du chargement différé...")
self._image = Image(self.filename)
else:
print("Proxy : l'image est déjà chargée, affichage direct")
self._image.display()
# Exemple d'utilisation
def demo_image_proxy():
print("Création du proxy d'image (l'image ne sera pas chargée immédiatement)")
proxy = ImageProxy("large_photo.jpg")
print("\n"Premier appel à display() - déclenche le chargement différé :")
proxy.display()
print("\n"Deuxième appel à display() - utilise directement l'image déjà chargée :")
proxy.display()
# Exécuter l'exemple
demo_image_proxy()
Exemple de proxy de protection : contrôle d'accès
Les proxys de protection sont utilisés pour contrôler l'accès aux ressources sensibles.
Exemple
"""Classe de données sensibles"""
def __init__(self, data: str):
self.data = data
def read_data(self) -> str:
"""Lire les données sensibles"""
return f"Données sensibles : {self.data}"
class ProtectionProxy:
"""Proxy de protection, implémentant le contrôle d'accès"""
def __init__(self, sensitive_data: SensitiveData, user_role: str):
self._sensitive_data = sensitive_data
self.user_role = user_role
def read_data(self) -> str:
"""Lire les données, mais effectuer une vérification des autorisations"""
if self.user_role != "admin":
return "Erreur : autorisations insuffisantes, seuls les administrateurs peuvent accéder aux données sensibles"
return self._sensitive_data.read_data()
# Exemple d'utilisation
def demo_protection_proxy():
sensitive_data = SensitiveData("Informations confidentielles : le budget du projet est de 1 million yuan")
# Un utilisateur ordinaire tente d'accéder
user_proxy = ProtectionProxy(sensitive_data, "user")
print("Un utilisateur ordinaire tente d'accéder :")
print(user_proxy.read_data())
# L'administrateur accède
admin_proxy = ProtectionProxy(sensitive_data, "admin")
print("\nAccès administrateur :)
print(admin_proxy.read_data())
demo_protection_proxy()
Avantages et inconvénients du patron Proxy
Avantages
- Contrôler l'accès aux objets: Le proxy peut contrôler comment et quand le client accède à l'objet réel
- Optimisation du chargement différé: Les proxies virtuels peuvent retarder la création d'objets coûteux, améliorant ainsi les performances
- Sécurité renforcée: Le proxy de protection peut ajouter une logique de contrôle d'accès
- Principe ouvert/fermé: De nouveaux proxies peuvent être introduits sans modifier le code client
- Séparation des préoccupations: Le proxy peut gérer des fonctions auxiliaires sans rapport avec la logique métier principale
Inconvénients
- Complexité accrue: L'introduction d'une nouvelle couche d'abstraction rend la structure du code plus complexe
- Latence de réponse: Le proxy peut augmenter le temps de traitement des requêtes
- Possibilité de sur-ingénierie: Pour des scénarios simples, l'utilisation d'un proxy peut sembler inutile
Scénarios d'application du patron Proxy
Le patron Proxy est particulièrement utile dans les scénarios suivants :
1. Chargement différé
Lorsque la création d'un objet est coûteuse mais que l'objet peut ne pas être utilisé immédiatement.
2. Contrôle d'accès
Lorsque les droits d'accès à certains objets doivent être restreints.
3. Représentant local (représentation distante)
Fournir une interface locale pour des objets distants, tels que les appels RPC.
4. Journalisation
Ajouter une journalisation avant et après les appels de méthode.
5. Mise en cache
Fournir une mise en cache pour les résultats d'opérations coûteuses.
Exercices pratiques
Pour renforcer votre compréhension du patron Proxy, essayez de réaliser les exercices suivants :
Exercice 1 : Proxy de mise en cache
Créez un proxy de mise en cache qui ajoute une fonctionnalité de mise en cache à une fonction qui calcule la suite de Fibonacci :
Exemple
"""Calcule la suite de Fibonacci"""
def fibonacci(self, n: int) -> int:
if n <= 1:
return n
return self.fibonacci(n - 1) + self.fibonacci(n - 2)
# Votre tâche : implémenter la classe FibonacciCacheProxy
# Exigence : ajouter une mise en cache au calcul de Fibonacci pour éviter les calculs répétés
class FibonacciCacheProxy:
def __init__(self):
self._calculator = FibonacciCalculator()
self._cache = {} # Implémenter la logique de mise en cache ici
def fibonacci(self, n: int) -> int:
# Implémenter la logique de mise en cache
pass
# Code de test
def test_cache_proxy():
proxy = FibonacciCacheProxy()
print("Premier calcul de fib(10) :")
result1 = proxy.fibonacci(10)
print(f"Résultat : {result1}")
print("Deuxième calcul de fib(10) (devrait être obtenu à partir du cache) :")
result2 = proxy.fibonacci(10)
print(f"Résultat : {result2}")
print(f"Les deux résultats sont identiques : {result1 == result2}")
Exercice 2 : Proxy de journalisation
Créez un proxy de journalisation qui enregistre les informations sur les appels de méthode :
Exemple
"""Service de base de données"""
def query(self, sql: str) -> str:
return f"Exécution de la requête : {sql}"
def update(self, sql: str) -> str:
return f"Exécution de la mise à jour : {sql}"
# Votre tâche : implémenter la classe LoggingProxy
# Exigence : enregistrer l'heure d'appel, les paramètres et le résultat de chaque méthode
class LoggingProxy:
def __init__(self):
self._service = DatabaseService()
def query(self, sql: str) -> str:
# Ajouter la logique de journalisation
pass
def update(self, sql: str) -> str:
# Ajouter la logique de journalisation
pass
Résumé
Le patron Proxy est un patron de conception puissant qui contrôle l'accès aux objets réels en introduisant une couche intermédiaire. L'implémentation du patron Proxy en Python est relativement simple, principalement grâce aux fonctionnalités dynamiques de Python.
Points clés
- Le proxy est un médiateur: Il agit comme un intermédiaire entre le client et l'objet réel
- Interface cohérente: Le proxy et l'objet réel implémentent la même interface
- Contrôle d'accès: Le proxy peut ajouter une logique de contrôle supplémentaire, comme le chargement paresseux, les vérifications de permissions, etc.
- Application flexible: Choisir différents types de proxies selon les besoins