Patrons de conception Python
Les patrons de conception sont, pour des problèmes courants du développement logiciel,des solutions réutilisables. Ils ne sont pas des conceptions complètes qui peuvent être directement converties en code, mais plutôt, pour résoudre des problèmes spécifiques,des modèles ou des plans.。
Valeur essentielle des patrons de conception
Les patrons de conception sont comme des plans standard dans le domaine de la construction, offrant les avantages suivants pour le développement logiciel :
- Améliorer la réutilisabilité du code: Éviter de réinventer la roue
- Améliorer la maintenabilité du code: Rendre la structure du code plus claire
- Favoriser la collaboration en équipe: Fournir un langage de programmation et une façon de penser unifiés
- Améliorer la qualité du code: Les solutions testées en pratique sont plus fiables
Analogie des patrons de conception dans la vie quotidienne
Imaginez que vous allez construire une maison :
- Manière primitive: Concevoir à partir de zéro à chaque fois est source d'erreurs et inefficace
- Utilisation des patrons de conception: Utiliser des plans d'architecture standard, en connaissant les agencements standard pour le salon, la cuisine et la chambre
Les patrons de conception sont les « plans d'architecture standard » du développement logiciel.
Types de patrons de conception
Selon le livre classique« Design Patterns - Elements of Reusable Object-Oriented Software » (traduction chinoise : « Design Patterns: Reusable Object-Oriented Software Elements »)Selon sa définition, il existe 23 patrons de conception classiques. Ces patrons peuvent être divisés en trois grandes catégories selon leur objectif : les patrons de création, les patrons structurels et les patrons comportementaux. De plus, dans le développement d'entreprise, il existe également une classe couramment utilisée de patrons de conception architecturale.
| Number | Patron et description | Comprend |
|---|---|---|
| 1 | Patrons de création Utilisés pour résoudre les problèmes de création d'objets, en encapsulant la logique d'instanciation, en masquant les détails d'implémentation concrets, ce qui rend le système plus flexible et extensible lors de la création d'objets. |
|
| 2 | Patrons structurels Se concentrent sur la composition et la collaboration des classes et des objets, ce qui nous aide à construire des structures système flexibles, réutilisables et extensibles. |
|
| 3 | Patrons comportementaux Mettre l'accent sur la communication et l'attribution des responsabilités entre les objets, en améliorant la flexibilité et la maintenabilité du système grâce à l'encapsulation d'algorithmes, d'états ou de logique de requête. |
|
| 4 | Patrons d'architecture de niveau entreprise Ces patrons sont couramment utilisés dans les architectures en couches des grands systèmes, en mettant l'accent sur la collaboration entre la couche de présentation et la couche de logique métier. |
|
L'image ci-dessous montre les relations et la structure hiérarchique entre les différents patrons de conception :
Avantages des patrons de conception
- Fournir un langage de conception unifié, permettant aux développeurs de communiquer rapidement l'intention de conception.
- Fournir des solutions matures et réutilisables aux problèmes courants.
- Réduire le couplage du système et améliorer la maintenabilité et l'extensibilité du code.
- Réduire la conception redondante, en améliorant l'efficacité de développement et la qualité du code.
- Aider les nouveaux membres à comprendre rapidement l'architecture du système et la philosophie de conception.
Six principes des patrons de conception
1. Principe ouvert/fermé
Ouvert à l'extension, fermé à la modification. Un logiciel doit s'adapter aux changements en étendant de nouvelles fonctionnalités plutôt qu'en modifiant le code existant. Le cœur de la mise en œuvre de ce principe réside dans l'utilisation d'abstractions (interfaces ou classes de base) pour définir un comportement stable.
2. Principe de substitution de Liskov
Les sous-classes doivent pouvoir remplacer tout endroit où une classe de base apparaît. L'héritage n'a de sens que lorsque les classes dérivées peuvent complètement remplacer la classe de base. Ce principe garantit la correction du polymorphisme et constitue un complément important au principe ouvert/fermé.
3. Principe d'inversion des dépendances
Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau ; les deux doivent dépendre d'abstractions. Les implémentations concrètes doivent dépendre d'interfaces ou de classes abstraites plutôt que directement de classes concrètes. Ce principe rend le système plus facile à étendre et à tester.
4. Principe de ségrégation des interfaces
Une classe ne doit pas dépendre d'interfaces dont elle n'a pas besoin. Les interfaces volumineuses doivent être divisées en interfaces plus petites et plus spécifiques afin de réduire le couplage et d'améliorer la flexibilité.
5. Loi de Déméter
Aussi appelé « principe du moindre savoir » : un objet doit minimiser ses interactions avec d'autres objets. En d'autres termes, une classe ne doit connaître que les objets directement liés à elle, ce qui réduit la complexité du système.
6. Principe de réutilisation par composition
Privilégiez l'utilisation de relations de composition ou d'agrégation pour la réutilisation plutôt que l'héritage. L'héritage crée un fort couplage, tandis que la composition permet un remplacement flexible des dépendances à l'exécution, ce qui aboutit à une conception structurelle plus élégante.
Exemples courants de patrons de conception en Python
Comprenons l'application des patrons de conception en Python à travers quelques exemples concrets.
Patron de conception Singleton
Le patron Singleton garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global.
Exemple
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
print("Création d'une nouvelle connexion à la base de données")
return cls._instance
def connect(self):
print("Connexion à la base de données")
# Exemple d'utilisation
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 et db2 sont-ils la même instance ? {db1 is db2}") # Sortie : True
Scénarios d'application:
- Pool de connexions à la base de données
- Gestionnaire de configuration
- Logger
Patron de conception Fabrique
Le patron de conception Fabrique fournit une interface pour créer des objets, permettant aux sous-classes de décider quelle classe instancier.
Exemple
# Produit abstrait
class Notification(ABC):
@abstractmethod
def send(self, message: str):
pass
# Produit concret
class EmailNotification(Notification):
def send(self, message: str):
print(f"Sending email: {message}")
class SMSNotification(Notification):
def send(self, message: str):
print(f"Sending SMS: {message}")
# Classe Fabrique
class NotificationFactory:
@staticmethod
def create_notification(notification_type: str) -> Notification:
if notification_type == "email":
return EmailNotification()
elif notification_type == "sms":
return SMSNotification()
else:
raise ValueError("Type de notification non pris en charge")
# Exemple d'utilisation
email = NotificationFactory.create_notification("email")
sms = NotificationFactory.create_notification("sms")
email.send("Votre commande a été expédiée")
sms.send("Code de vérification : 123456")
Explication de code:
Notificationest une classe de base abstraite qui définit l'interfaceEmailNotificationetSMSNotificationest l'implémentation concrèteNotificationFactoryest responsable de la création d'objets de notification concrets
Patron Observateur
Le patron Observateur définit une dépendance de type un-à-plusieurs entre des objets. Lorsque l'état d'un objet change, tous les objets qui en dépendent sont notifiés.
Exemple
# Interface Observateur
class Observer(ABC):
@abstractmethod
def update(self, message: str):
pass
# Observateur concret
class EmailSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} a reçu une notification par e-mail : {message}")
class SMSSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, message: str):
print(f"{self.name} a reçu une notification par SMS : {message}")
# Sujet (Observable)
class NewsPublisher:
def __init__(self):
self._subscribers = []
def subscribe(self, subscriber: Observer):
self._subscribers.append(subscriber)
def unsubscribe(self, subscriber: Observer):
self._subscribers.remove(subscriber)
def notify_subscribers(self, message: str):
for subscriber in self._subscribers:
subscriber.update(message)
# Exemple d'utilisation
publisher = NewsPublisher()
# Créer des abonnés
alice = EmailSubscriber("Alice")
bob = SMSSubscriber("Bob")
# S'abonner aux actualités
publisher.subscribe(alice)
publisher.subscribe(bob)
# Publier les actualités
publisher.notify_subscribers("Python 3.12 est sorti !")
# Se désabonner
publisher.unsubscribe(alice)
publisher.notify_subscribers("Ceci est une notification que seul Bob peut voir")
Considérations particulières pour les patrons de conception en Python
La nature dynamique de Python
En tant que langage dynamique, Python rend certains patrons de conception plus simples à implémenter :
Exemple
def create_payment(method):
payment_methods = {
'credit_card': CreditCardPayment,
'paypal': PayPalPayment,
'alipay': AlipayPayment
}
return payment_methods[method]()
# À utiliser directement, aucune hiérarchie de classes complexe nécessaire
payment = create_payment('alipay')
Prise en charge intégrée du patron Decorator
Python dispose d'une syntaxe de décorateur intégrée, ce qui rend le patron Decorator plus élégant à implémenter :
Exemple
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__} temps d'exécution: {end - start:.2f} secondes")
return result
return wrapper
@log_execution_time
def process_data(data):
# Simuler le traitement des données
import time
time.sleep(1)
return f"Données traitées: {data}"
# Utilisation
result = process_data("Données d'exemple")