Python-Strategiemuster

Das Strategiemuster ist ein Verhaltens-Entwurfsmuster, das es Ihnen ermöglicht, zur Laufzeit einen Algorithmus oder ein Verhalten auszuwählen. Einfach gesagt, kapselt das Strategiemuster verschiedene Algorithmen in unabhängige Klassen, sodass sie sich gegenseitig ersetzen können.

Kernidee

Stellen Sie sich vor, Sie gehen in ein Restaurant, um Essen zu bestellen: Sie können verschiedene Zahlungsmethoden wählen – Bargeld, Kreditkarte, mobile Zahlung usw. Egal, welche Zahlungsmethode Sie wählen, Sie können die Zahlung letztendlich abschließen, aber der konkrete Zahlungsvorgang ist unterschiedlich. Dies ist ein reales Beispiel für das Strategiemuster.

Entwurfsprinzipien

Das Strategie-Muster folgt den folgenden wichtigen Prinzipien:

  • Open-Closed-Prinzip: Offen für Erweiterung, geschlossen für Modifikation.
  • Single-Responsibility-Prinzip: Jede Strategieklasse ist nur für einen Algorithmus verantwortlich.
  • Dependency-Inversion-Prinzip: Von Abstraktionen abhängen, nicht von konkreten Implementierungen.

Struktur des Strategie-Musters

Lassen Sie uns die Zusammensetzung des Strategie-Musters anhand eines UML-Klassendiagramms verstehen:

Beschreibung der Komponenten

Kontext

  • Hält eine Referenz auf ein Strategieobjekt.
  • Kann Strategien dynamisch wechseln.
  • Delegiert Arbeit an das Strategieobjekt.

Strategie (Strategie-Schnittstelle)

  • Definiert die gemeinsame Schnittstelle für alle unterstützten Algorithmen.
  • Deklariert die Methode zur Ausführung des Algorithmus.

ConcreteStrategy

  • Implementiert die Strategie-Schnittstelle.
  • Stellt eine konkrete Algorithmusimplementierung bereit.

Grundlegende Syntax und Implementierung

Definition der Strategie-Schnittstelle

In Python können wir eine abstrakte Basisklasse (ABC) verwenden, um die Strategie-Schnittstelle zu definieren:

Beispiel

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    """Abstrakte Basisklasse für Zahlungsstrategie"""
   
    @abstractmethod
    def pay(self, amount: float) -> bool:
        """Zahlungsmethode"""
        pass

Konkrete Strategie-Implementierung

Beispiel

class CreditCardPayment(PaymentStrategy):
    """Kreditkarten-Zahlungsstrategie"""
   
    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"Zahle {amount} yuan mit Kreditkarte")
        print(f"Kartennummer: {self.card_number}")
        # Die tatsächliche Zahlungslogik sollte hier stehen
        return True

class AlipayPayment(PaymentStrategy):
    """Alipay-Zahlungsstrategie"""
   
    def __init__(self, account: str):
        self.account = account
   
    def pay(self, amount: float) -> bool:
        print(f"Zahle {amount} yuan mit Alipay")
        print(f"Alipay-Konto: {self.account}")
        # Die tatsächliche Zahlungslogik sollte hier stehen
        return True

class WechatPayment(PaymentStrategy):
    """WeChat-Pay-Strategie"""
   
    def __init__(self, openid: str):
        self.openid = openid
   
    def pay(self, amount: float) -> bool:
        print(f"Zahle {amount} yuan mit WeChat Pay")
        print(f"WeChat-OpenID: {self.openid}")
        # Die tatsächliche Zahlungslogik sollte hier stehen
        return True

Implementierung der Kontextklasse

Beispiel

class PaymentContext:
    """Zahlungskontextklasse"""
   
    def __init__(self, strategy: PaymentStrategy = None):
        self._strategy = strategy
   
    def set_strategy(self, strategy: PaymentStrategy):
        """Zahlungsstrategie festlegen"""
        self._strategy = strategy
   
    def execute_payment(self, amount: float) -> bool:
        """Zahlung ausführen"""
        if not self._strategy:
            raise ValueError("Keine Zahlungsstrategie festgelegt")
       
        return self._strategy.pay(amount)

Vollständiges Beispiel: E-Commerce-Zahlungssystem

Lassen Sie uns die praktische Anwendung des Strategiemusters anhand eines vollständigen E-Commerce-Zahlungssystems demonstrieren:

Beispiel

from abc import ABC, abstractmethod
from typing import Dict, Any

# Strategie-Schnittstelle
class DiscountStrategy(ABC):
    """Rabattstrategie-Schnittstelle"""
   
    @abstractmethod
    def calculate_discount(self, original_price: float) -> float:
        """Preis nach Rabatt berechnen"""
        pass

# Konkrete Strategieimplementierungen
class NoDiscountStrategy(DiscountStrategy):
    """Keine-Rabatt-Strategie"""
   
    def calculate_discount(self, original_price: float) -> float:
        return original_price

class PercentageDiscountStrategy(DiscountStrategy):
    """Prozentuale-Rabatt-Strategie"""
   
    def __init__(self, percentage: float):
        if not 0 <= percentage <= 100:
            raise ValueError("Der Rabattprozentsatz muss zwischen 0 und 100 liegen")
        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):
    """Strategie für festen Rabattbetrag"""
   
    def __init__(self, discount_amount: float):
        if discount_amount < 0:
            raise ValueError("Der Rabattbetrag darf nicht negativ sein")
        self.discount_amount = discount_amount
   
    def calculate_discount(self, original_price: float) -> float:
        return max(0, original_price - self.discount_amount)

class SeasonalDiscountStrategy(DiscountStrategy):
    """Saisonale Rabattstrategie"""
   
    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)

# Kontextklasse
class ShoppingCart:
    """Warenkorbklasse"""
   
    def __init__(self):
        self.items = []
        self._discount_strategy = NoDiscountStrategy()
   
    def add_item(self, item: str, price: float):
        """Artikel hinzufügen"""
        self.items.append({"item": item, "price": price})
   
    def set_discount_strategy(self, strategy: DiscountStrategy):
        """Rabattstrategie festlegen"""
        self._discount_strategy = strategy
   
    def calculate_total(self) -> float:
        """Gesamtpreis berechnen"""
        total = sum(item["price"] for item in self.items)
        return self._discount_strategy.calculate_discount(total)
   
    def display_cart(self):
        """Warenkorbinhalt anzeigen"""
        print("Warenkorbinhalt:")
        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"Originalpreis: {original_total} yuan")
        print(f"Reduzierter Preis: {final_total} yuan")
       
        if original_total != final_total:
            discount = original_total - final_total
            print(f"Ersparnis: {discount} yuan")

# Verwendungsbeispiel
def main():
    # Warenkorb erstellen
    cart = ShoppingCart()
   
    # Artikel hinzufügen
    cart.add_item("Python-Programmierbuch", 89.0)
    cart.add_item("Funkmaus", 129.0)
    cart.add_item("Mechanische Tastatur", 399.0)
   
    print("=== Kein Rabatt ===")
    cart.set_discount_strategy(NoDiscountStrategy())
    cart.display_cart()
   
    print("\n"=== 20 % Rabatt ===")
    cart.set_discount_strategy(PercentageDiscountStrategy(20))  # 20 % Rabatt
    cart.display_cart()
   
    print("\n"=== Schwellenwertrabatt (50 Yuan Nachlass) ===")
    cart.set_discount_strategy(FixedAmountDiscountStrategy(50))
    cart.display_cart()
   
    print("\n"=== Saisonrabatt ===")
    cart.set_discount_strategy(SeasonalDiscountStrategy(30, 1.5))  # Basisrabatt 30, Saisonkoeffizient 1.5
    cart.display_cart()

if __name__ == "__main__":
    main()

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

=== 无折扣 ===
购物车内容:
  - 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元

Fortgeschrittene Nutzung des Strategie-Musters

1. Strategie-Fabrikmuster

Kombiniere das Fabrikmuster, um die Erstellung von Strategien zu verwalten:

Beispiel

class DiscountStrategyFactory:
    """Rabattstrategie-Fabrik"""
   
    @staticmethod
    def create_strategy(strategy_type: str, **kwargs) -> DiscountStrategy:
        """Rabattstrategie erstellen"""
        strategies = {
            "no_discount": NoDiscountStrategy,
            "percentage": PercentageDiscountStrategy,
            "fixed_amount": FixedAmountDiscountStrategy,
            "seasonal": SeasonalDiscountStrategy
        }
       
        if strategy_type not in strategies:
            raise ValueError(f"Nicht unterstützter Strategietyp: {strategy_type}")
       
        return strategies[strategy_type](**kwargs)

# Fabrikmuster verwenden
factory = DiscountStrategyFactory()

# Verschiedene Strategien erstellen
strategy1 = factory.create_strategy("percentage", percentage=15)  # 15% Rabatt
strategy2 = factory.create_strategy("fixed_amount", discount_amount=100)  # 100 yuan Rabatt

2. Dynamische Strategieauswahl

Strategien dynamisch anhand von Bedingungen auswählen:

Beispiel

class DynamicDiscountSelector:
    """Dynamischer Rabattselektor"""
   
    @staticmethod
    def select_strategy(user_type: str, total_amount: float) -> DiscountStrategy:
        """Strategie basierend auf Benutzertyp und Gesamtbetrag auswählen"""
        if user_type == "vip":
            if total_amount > 500:
                return PercentageDiscountStrategy(25)  # VIP: 25% Rabatt auf Bestellungen ab 500 oder mehr
            else:
                return PercentageDiscountStrategy(15)  # VIP: 15% Rabatt
        elif user_type == "normal":
            if total_amount > 300:
                return FixedAmountDiscountStrategy(30)  # Normale Benutzer: 30 Yuan Rabatt auf Bestellungen ab 300 oder mehr
            else:
                return NoDiscountStrategy()
        else:
            return NoDiscountStrategy()

# Dynamische Auswahl verwenden
cart = ShoppingCart()
cart.add_item("Produkt 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()

Vorteile und Anwendungsszenarien des Strategiemusters

Vorteilsvergleich

Merkmal Traditioneller Ansatz Strategie-Muster
Erweiterbarkeit Erfordert die Änderung vorhandenen Codes Einfach eine neue Strategieklasse hinzufügen
Wartbarkeit Hohe Code-Kopplung Trennung von Zuständigkeiten, einfach zu warten
Flexibilität Schwierig, Algorithmen zur Laufzeit zu wechseln Strategien können dynamisch gewechselt werden
Testbarkeit Schwierig, Algorithmen isoliert zu testen Jede Strategie kann unabhängig getestet werden

Geeignete Szenarien

  1. Mehrere Algorithmusvarianten: Wenn Sie mehrere ähnliche Klassen haben, die sich nur in bestimmten Verhaltensweisen unterscheiden.
  2. Bedingte Anweisungen vermeiden: Wenn Sie vermeiden möchten, eine große Anzahl bedingter Anweisungen (if-else oder switch) zu verwenden.
  3. Algorithmusauswahl zur Laufzeit: Wenn Sie zur Laufzeit verschiedene Algorithmen auswählen müssen.
  4. Algorithmuskapselung: Wenn Sie Algorithmusdetails von den Clients isolieren möchten, die den Algorithmus verwenden.

Ungeeignete Szenarien

  1. Einfache Algorithmen: Wenn es nur einen oder zwei Algorithmen gibt, die sich selten ändern, kann es Over-Engineering sein.
  2. Client muss Strategiedetails kennen: Wenn der Client die konkrete Implementierung der Strategie kennen muss.
  3. Zu viele Strategien: Wenn die Anzahl der Strategieklassen explodiert, sollten Sie andere Muster in Betracht ziehen.

Best Practices und Überlegungen

Vorschläge zur Codeorganisation

Beispiel

# Empfohlene Dateistruktur
project/
├── strategies/
│   ├── __init__.py
│ ├── base_strategy.py      # Basisstrategie-Schnittstelle
│ ├── discount_strategies.py # Rabattbezogene Strategien
│ └── payment_strategies.py # Zahlungsbezogene Strategien
├── contexts/
│   ├── __init__.py
│ └── shopping_cart.py      # Kontextklasse
└── main.py

Fehlerbehandlung

Beispiel

class SafeDiscountStrategy(DiscountStrategy):
    """Rabattstrategie mit Fehlerbehandlung"""
   
    def __init__(self, base_strategy: DiscountStrategy, fallback_strategy: DiscountStrategy = Keine):
        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"Fehler bei der Rabattberechnung: {e}, Fallback-Strategie wird verwendet")
            return self.fallback_strategy.calculate_discount(original_price)

Leistungsüberlegungen

Für leistungssensible Szenarien sollten die folgenden Optimierungen in Betracht gezogen werden:

Beispiel

from functools import lru_cache

class CachedDiscountStrategy(DiscountStrategy):
    """Rabattstrategie mit Caching"""
   
    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)

Praktische Übungen

Übung 1: Implementierung der Sortierstrategie

Implementieren Sie einen Sortierer, der mehrere Sortieralgorithmen unterstützt:

Beispiel

from abc import ABC, abstractmethod
from typing import List

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: List) -> List:
        pass

# TODO: Bubble-Sort-Strategie implementieren
class BubbleSortStrategy(SortStrategy):
    def sort(self, data: List) -> List:
        # Dein Implementierungscode
        pass

# TODO: Quick-Sort-Strategie implementieren
class QuickSortStrategy(SortStrategy):
    def sort(self, data: List) -> List:
        # Dein Implementierungscode
        pass

# TODO: Merge-Sort-Strategie implementieren
class MergeSortStrategy(SortStrategy):
    def sort(self, data: List) -> List:
        # Dein Implementierungscode
        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("Sortierstrategie nicht gesetzt")
        return self._strategy.sort(data)

# Testen Sie Ihre Implementierung
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSortStrategy())
result = sorter.sort_data(data)
print(f"Sortierergebnis: {result}")

Übung 2: Dateikomprimierungsstrategie

Entwerfen Sie einen Dateikompressor, der mehrere Komprimierungsformate unterstützt:

Beispiel

from abc import ABC, abstractmethod

class CompressionStrategy(ABC):
    @abstractmethod
    def compress(self, file_path: str) -> str:
        pass
   
    @abstractmethod
    def decompress(self, file_path: str) -> str:
        pass

# TODO: ZIP-Komprimierungsstrategie implementieren
class ZipCompressionStrategy(CompressionStrategy):
    def compress(self, file_path: str) -> str:
        # Dein Implementierungscode
        pass
   
    def decompress(self, file_path: str) -> str:
        # Dein Implementierungscode
        pass

# TODO: GZIP-Komprimierungsstrategie implementieren
class GzipCompressionStrategy(CompressionStrategy):
    def compress(self, file_path: str) -> str:
        # Dein Implementierungscode
        pass
   
    def decompress(self, file_path: str) -> str:
        # Dein Implementierungscode
        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("Komprimierungsstrategie nicht festgelegt")
        return self._strategy.compress(file_path)
   
    def decompress_file(self, file_path: str) -> str:
        if not self._strategy:
            raise ValueError("Komprimierungsstrategie nicht festgelegt")
        return self._strategy.decompress(file_path)

Zusammenfassung

Das Strategie-Muster ist ein sehr praktisches Entwurfsmuster in Python. Durch die Kapselung von Algorithmen in unabhängige Strategieklassen bietet es gute Erweiterbarkeit und Flexibilität. Nach dem Studium dieses Artikels sollten Sie in der Lage sein:

  1. die Kernkonzepte und Einsatzszenarien des Strategie-Musters verstehen
  2. Beherrsche die grundlegenden Implementierungsmethoden des Strategie-Musters
  3. Wende das Strategie-Muster in realen Projekten angemessen an
  4. Vermeide häufige Fehlanwendungen des Strategie-Musters
Andere Erweiterungen