Patron de stratégie Python
Le modèle de stratégie est un patron de conception comportemental qui vous permet de choisir un algorithme ou un comportement à l’exécution. En termes simples, le modèle de stratégie encapsule différents algorithmes dans des classes indépendantes, leur permettant de se remplacer mutuellement.
Idée centrale
Imaginez que vous allez au restaurant pour commander à manger : vous pouvez choisir différents modes de paiement - espèces, carte de crédit, paiement mobile, etc. Quel que soit le mode de paiement que vous choisissez, vous pouvez finalement effectuer le paiement, mais le processus de paiement spécifique est différent. C’est un exemple concret du modèle de stratégie.
Principes de conception
Le patron de conception Stratégie suit les principes importants suivants :
- Principe ouvert/fermé: ouvert à l'extension, fermé à la modification.
- Principe de responsabilité unique: chaque classe de stratégie n'est responsable que d'un seul algorithme.
- Principe d'inversion des dépendances: dépendez d'abstractions, pas d'implémentations concrètes.
Structure du patron de conception Stratégie
Comprenons la composition du patron de conception Stratégie à l'aide d'un diagramme de classes UML :

Description des composants
Contexte
- Maintient une référence à un objet stratégie.
- Peut changer dynamiquement de stratégie.
- Délègue le travail à l'objet stratégie.
Stratégie (interface Strategy)
- Définit l'interface commune pour tous les algorithmes pris en charge.
- Déclare la méthode d'exécution de l'algorithme.
ConcreteStrategy
- Implémente l'interface de stratégie.
- Fournit une implémentation concrète de l'algorithme.
Syntaxe de base et implémentation
Définition de l'interface de stratégie
En Python, nous pouvons utiliser une classe de base abstraite (ABC) pour définir l'interface de stratégie :
Exemple
class PaymentStrategy(ABC):
"""Classe de base abstraite de la stratégie de paiement"""
@abstractmethod
def pay(self, amount: float) -> bool:
"""Méthode de paiement"""
pass
Stratégie concrète
Exemple
"""Stratégie de paiement par carte de crédit"""
def __init__(self, card_number: str, expiry_date: str, cvv: str):
self.card_number = card_number
self.expiry_date = expiry_date
self.cvv = cvv
def pay(self, amount: float) -> bool:
print(f"Payer {amount} yuan par carte de crédit")
print(f"Numéro de carte : {self.card_number}")
# La logique de paiement réelle doit être ici
return True
class AlipayPayment(PaymentStrategy):
"""Stratégie de paiement Alipay"""
def __init__(self, account: str):
self.account = account
def pay(self, amount: float) -> bool:
print(f"Payer {amount} yuan avec Alipay")
print(f"Compte Alipay : {self.account}")
# La logique de paiement réelle doit être ici
return True
class WechatPayment(PaymentStrategy):
"""Stratégie de paiement WeChat Pay"""
def __init__(self, openid: str):
self.openid = openid
def pay(self, amount: float) -> bool:
print(f"Payer {amount} yuan avec WeChat Pay")
print(f"OpenID WeChat : {self.openid}")
# La logique de paiement réelle doit être ici
return True
Implémentation de la classe de contexte
Exemple
"""Classe de contexte de paiement"""
def __init__(self, strategy: PaymentStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: PaymentStrategy):
"""Définir la stratégie de paiement"""
self._strategy = strategy
def execute_payment(self, amount: float) -> bool:
"""Exécuter le paiement"""
if not self._strategy:
raise ValueError("Aucune stratégie de paiement définie")
return self._strategy.pay(amount)
Exemple complet : Système de paiement e-commerce
Démontrons l'application pratique du patron de stratégie à travers un système de paiement e-commerce complet :
Exemple
from typing import Dict, Any
# Interface de stratégie
class DiscountStrategy(ABC):
"""Interface de stratégie de réduction"""
@abstractmethod
def calculate_discount(self, original_price: float) -> float:
"""Calculer le prix après réduction"""
pass
# Implémentations concrètes des stratégies
class NoDiscountStrategy(DiscountStrategy):
"""Stratégie sans réduction"""
def calculate_discount(self, original_price: float) -> float:
return original_price
class PercentageDiscountStrategy(DiscountStrategy):
"""Stratégie de réduction en pourcentage"""
def __init__(self, percentage: float):
if not 0 <= percentage <= 100:
raise ValueError("Le pourcentage de remise doit être compris entre 0-100")
self.percentage = percentage
def calculate_discount(self, original_price: float) -> float:
discount_amount = original_price * (self.percentage / 100)
return original_price - discount_amount
class FixedAmountDiscountStrategy(DiscountStrategy):
"""Stratégie de remise à montant fixe"""
def __init__(self, discount_amount: float):
if discount_amount < 0:
raise ValueError("Le montant de la remise ne peut pas être négatif")
self.discount_amount = discount_amount
def calculate_discount(self, original_price: float) -> float:
return max(0, original_price - self.discount_amount)
class SeasonalDiscountStrategy(DiscountStrategy):
"""Stratégie de remise saisonnière"""
def __init__(self, base_discount: float, seasonal_multiplier: float):
self.base_discount = base_discount
self.seasonal_multiplier = seasonal_multiplier
def calculate_discount(self, original_price: float) -> float:
total_discount = self.base_discount * self.seasonal_multiplier
return max(0, original_price - total_discount)
# Classe de contexte
class ShoppingCart:
"""Classe de panier d'achat"""
def __init__(self):
self.items = []
self._discount_strategy = NoDiscountStrategy()
def add_item(self, item: str, price: float):
"""Ajouter un article"""
self.items.append({"item": item, "price": price})
def set_discount_strategy(self, strategy: DiscountStrategy):
"""Définir la stratégie de remise"""
self._discount_strategy = strategy
def calculate_total(self) -> float:
"""Calculer le prix total"""
total = sum(item["price"] for item in self.items)
return self._discount_strategy.calculate_discount(total)
def display_cart(self):
"""Afficher le contenu du panier"""
print("Contenu du panier :")
for item in self.items:
print(f" - {item['item']}: {item['price']} yuan")
original_total = sum(item["price"] for item in self.items)
final_total = self.calculate_total()
print(f"Prix d'origine : {original_total} yuan")
print(f"Prix réduit : {final_total} yuan")
if original_total != final_total:
discount = original_total - final_total
print(f"Économies : {discount} yuan")
# Exemple d'utilisation
def main():
# Créer un panier d'achat
cart = ShoppingCart()
# Ajouter des articles
cart.add_item("Livre de programmation Python", 89.0)
cart.add_item("Souris sans fil", 129.0)
cart.add_item("Clavier mécanique", 399.0)
print("=== Aucune remise ===")
cart.set_discount_strategy(NoDiscountStrategy())
cart.display_cart()
print("\n"=== 20% de réduction ===")
cart.set_discount_strategy(PercentageDiscountStrategy(20)) # 20% de réduction
cart.display_cart()
print("\n"=== Remise sur seuil (50 yuan de réduction) ===")
cart.set_discount_strategy(FixedAmountDiscountStrategy(50))
cart.display_cart()
print("\n"=== Remise saisonnière ===")
cart.set_discount_strategy(SeasonalDiscountStrategy(30, 1.5)) # Remise de base 30, coefficient saisonnier 1.5
cart.display_cart()
if __name__ == "__main__":
main()
En exécutant le code ci-dessus, vous verrez la sortie suivante :
=== 无折扣 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 617.0元 === 8折优惠 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 493.6元 节省: 123.4元 === 满减优惠(减50元)=== 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 567.0元 节省: 50.0元 === 季节性优惠 === 购物车内容: - Python编程书: 89.0元 - 无线鼠标: 129.0元 - 机械键盘: 399.0元 原价: 617.0元 折后价: 572.0元 节省: 45.0元
Utilisation avancée du patron de stratégie
1. Patron de fabrique de stratégies
Combinez le patron de fabrique pour gérer la création de stratégies :
Exemple
"""Fabrique de stratégies de remise"""
@staticmethod
def create_strategy(strategy_type: str, **kwargs) -> DiscountStrategy:
"""Créer une stratégie de remise"""
strategies = {
"no_discount": NoDiscountStrategy,
"percentage": PercentageDiscountStrategy,
"fixed_amount": FixedAmountDiscountStrategy,
"seasonal": SeasonalDiscountStrategy
}
if strategy_type not in strategies:
raise ValueError(f"Type de stratégie non pris en charge : {strategy_type}")
return strategies[strategy_type](**kwargs)
# Utiliser le patron de fabrique
factory = DiscountStrategyFactory()
# Créer différentes stratégies
strategy1 = factory.create_strategy("percentage", percentage=15) # 15% de réduction
strategy2 = factory.create_strategy("fixed_amount", discount_amount=100) # 100 yuan de réduction
2. Sélection dynamique des stratégies
Sélectionner dynamiquement les stratégies en fonction des conditions :
Exemple
"""Sélecteur dynamique de remise"""
@staticmethod
def select_strategy(user_type: str, total_amount: float) -> DiscountStrategy:
"""Sélectionner la stratégie en fonction du type d'utilisateur et du montant total"""
if user_type == "vip":
if total_amount > 500:
return PercentageDiscountStrategy(25) # VIP : 25% de réduction pour les commandes de 500 ou plus
else:
return PercentageDiscountStrategy(15) # VIP : 15% de réduction
elif user_type == "normal":
if total_amount > 300:
return FixedAmountDiscountStrategy(30) # Utilisateurs réguliers : 30 yuan de réduction pour les commandes de 300 ou plus
else:
return NoDiscountStrategy()
else:
return NoDiscountStrategy()
# Utiliser la sélection dynamique
cart = ShoppingCart()
cart.add_item("Product A", 200)
cart.add_item("Product B", 150)
selector = DynamicDiscountSelector()
strategy = selector.select_strategy("vip", cart.calculate_total())
cart.set_discount_strategy(strategy)
cart.display_cart()
Avantages et scénarios d'application du patron Stratégie
Comparaison des avantages
| Fonctionnalité | Approche traditionnelle | Patron Stratégie |
|---|---|---|
| Extensibilité | Nécessite de modifier le code existant | Ajouter simplement une nouvelle classe de stratégie |
| Kemudahan pemeliharaan | Fort couplage du code | Séparation des responsabilités, facile à maintenir |
| Flexibilité | Difficile de changer d'algorithme à l'exécution | Permet de changer dynamiquement de stratégie |
| Testabilité | Difficile de tester les algorithmes de manière isolée | Chaque stratégie peut être testée indépendamment |
Scénarios applicables
- Variantes multiples d'algorithmes: Lorsque vous avez plusieurs classes similaires qui ne diffèrent que par certains comportements.
- Éviter les instructions conditionnelles: Lorsque vous voulez éviter d'utiliser un grand nombre d'instructions conditionnelles (if-else ou switch).
- Sélection d'algorithme à l'exécution: Lorsque vous devez choisir différents algorithmes au moment de l'exécution.
- Encapsulation d'algorithme: Lorsque vous souhaitez isoler les détails de l'algorithme des clients qui utilisent l'algorithme.
Scénarios inapplicables
- Algorithmes simples: S'il n'y a qu'un ou deux algorithmes qui changent rarement, cela peut être de la sur-ingénierie.
- Le client doit connaître les détails de la stratégie: Si le client doit connaître l'implémentation concrète de la stratégie.
- Trop de stratégies: Lorsque le nombre de classes de stratégie explose, envisagez d'autres patrons.
Bonnes pratiques et considérations
Suggestions d'organisation du code
Exemple
project/
├── strategies/
│ ├── __init__.py
│ ├── base_strategy.py # Interface de stratégie de base
│ ├── discount_strategies.py # Stratégies liées aux remises
│ └── payment_strategies.py # Stratégies liées au paiement
├── contexts/
│ ├── __init__.py
│ └── shopping_cart.py # Classe de contexte
└── main.py
Gestion des erreurs
Exemple
"""Stratégie de remise avec gestion des erreurs"""
def __init__(self, base_strategy: DiscountStrategy, fallback_strategy: DiscountStrategy = Aucune):
self.base_strategy = base_strategy
self.fallback_strategy = fallback_strategy or NoDiscountStrategy()
def calculate_discount(self, original_price: float) -> float:
try:
return self.base_strategy.calculate_discount(original_price)
except Exception as e:
print(f"Erreur de calcul de remise : {e}, utilisation de la stratégie de secours")
return self.fallback_strategy.calculate_discount(original_price)
Considérations de performance
Pour les scénarios sensibles aux performances, envisagez les optimisations suivantes :
Exemple
class CachedDiscountStrategy(DiscountStrategy):
"""Stratégie de remise avec mise en cache"""
def __init__(self, base_strategy: DiscountStrategy):
self.base_strategy = base_strategy
@lru_cache(maxsize=128)
def calculate_discount(self, original_price: float) -> float:
return self.base_strategy.calculate_discount(original_price)
Exercices pratiques
Exercice 1 : Implémentation de la stratégie de tri
Implémentez un trieur qui prend en charge plusieurs algorithmes de tri :
Exemple
from typing import List
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: List) -> List:
pass
# TODO: Implémenter la stratégie de tri à bulles
class BubbleSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Votre code d'implémentation
pass
# TODO: Implémenter la stratégie de tri rapide
class QuickSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Votre code d'implémentation
pass
# TODO: Implémenter la stratégie de tri fusion
class MergeSortStrategy(SortStrategy):
def sort(self, data: List) -> List:
# Votre code d'implémentation
pass
class Sorter:
def __init__(self, strategy: SortStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self._strategy = strategy
def sort_data(self, data: List) -> List:
if not self._strategy:
raise ValueError("Stratégie de tri non définie")
return self._strategy.sort(data)
# Testez votre implémentation
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSortStrategy())
result = sorter.sort_data(data)
print(f"Résultat du tri : {result}")
Exercice 2 : Stratégie de compression de fichiers
Concevez un compresseur de fichiers qui prend en charge plusieurs formats de compression :
Exemple
class CompressionStrategy(ABC):
@abstractmethod
def compress(self, file_path: str) -> str:
pass
@abstractmethod
def decompress(self, file_path: str) -> str:
pass
# TODO: Implémenter la stratégie de compression ZIP
class ZipCompressionStrategy(CompressionStrategy):
def compress(self, file_path: str) -> str:
# Votre code d'implémentation
pass
def decompress(self, file_path: str) -> str:
# Votre code d'implémentation
pass
# TODO: Implémenter la stratégie de compression GZIP
class GzipCompressionStrategy(CompressionStrategy):
def compress(self, file_path: str) -> str:
# Votre code d'implémentation
pass
def decompress(self, file_path: str) -> str:
# Votre code d'implémentation
pass
class FileCompressor:
def __init__(self, strategy: CompressionStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: CompressionStrategy):
self._strategy = strategy
def compress_file(self, file_path: str) -> str:
if not self._strategy:
raise ValueError("Stratégie de compression non définie")
return self._strategy.compress(file_path)
def decompress_file(self, file_path: str) -> str:
if not self._strategy:
raise ValueError("Stratégie de compression non définie")
return self._strategy.decompress(file_path)
Résumé
Le patron de conception Strategy est un patron de conception très pratique en Python. En encapsulant les algorithmes dans des classes de stratégie indépendantes, il offre une bonne extensibilité et flexibilité. Après avoir étudié cet article, vous devriez être capable de :
- Comprendre les concepts fondamentaux et les scénarios d'application du patron de conception Strategy
- Maîtrisez les méthodes d'implémentation de base du patron Stratégie
- Appliquez le patron Stratégie de manière appropriée dans des projets réels
- Évitez les utilisations abusives courantes du patron Stratégie