Menu
Coddy logo textTech

Strategy-Muster

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 95 von 104.

Das Strategy-Pattern definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Dies ermöglicht es, das Verhalten eines Objekts zur Laufzeit zu ändern, ohne dessen Code zu modifizieren – der Algorithmus variiert unabhängig von den Clients, die ihn verwenden.

Das Muster besteht aus drei Teilen: einem Strategy-Interface, das die Algorithmus-Methode deklariert, konkreten Strategien, die verschiedene Variationen implementieren, und einem Kontext, der eine Strategie verwendet:

#include <iostream>
#include <memory>

// Strategy-Schnittstelle
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// Konkrete Strategien
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// Kontext
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

int main() {
    ShoppingCart cart;
    
    cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
    cart.checkout(100);
    
    cart.setPaymentMethod(std::make_unique<PayPalPayment>());
    cart.checkout(50);
}

Der ShoppingCart weiß nicht, welche Zahlungsmethode er verwendet – er ruft einfach pay() für die jeweils eingestellte Strategie auf. Sie können Strategien zur Laufzeit mit setPaymentMethod() austauschen, was das System flexibel und einfach um neue Zahlungsoptionen erweiterbar macht.

Verwenden Sie Strategy, wenn Sie mehrere Algorithmen für eine bestimmte Aufgabe haben und dynamisch zwischen ihnen wechseln möchten, oder wenn Sie bedingte Anweisungen zur Auswahl des Verhaltens vermeiden möchten.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen Versandkostenrechner erstellen, der das Strategy-Pattern verwendet, um Lieferkosten basierend auf verschiedenen Versandarten zu berechnen. Dies ist ein praktisches Szenario, in dem Sie Algorithmen zur Laufzeit austauschen müssen – dasselbe Paket könnte per Landweg, Luftfracht oder Express versendet werden, wobei jede Methode ihre eigene Preislogik hat.

Sie werden Ihren Code über drei Dateien organisieren:

  • ShippingStrategy.h: Definieren Sie Ihr Strategy-Interface und die konkreten Versandstrategien.

    Erstellen Sie eine abstrakte Klasse ShippingStrategy mit einer rein virtuellen Methode calculateCost(double weight), die die Versandkosten als double zurückgibt, sowie einen virtuellen Destruktor.

    Implementieren Sie dann drei konkrete Strategien:

    • GroundShipping — kostet 1.5 pro Gewichtseinheit (weight * 1.5)
    • AirShipping — kostet 4.0 pro Gewichtseinheit (weight * 4.0)
    • ExpressShipping — kostet 6.5 pro Gewichtseinheit plus eine Pauschalgebühr von 10.0 (weight * 6.5 + 10.0)
  • ShippingService.h: Erstellen Sie die Kontext-Klasse, die eine Versandstrategie verwendet.

    Ihre Klasse ShippingService sollte einen std::unique_ptr<ShippingStrategy> als privates Mitglied halten. Implementieren Sie:

    • Eine Methode setStrategy(std::unique_ptr<ShippingStrategy> strategy), um die Versandmethode zu ändern
    • Eine Methode calculateShipping(double weight), die die aktuelle Strategie verwendet, um die Kosten zu berechnen und zurückzugeben

    Wenn keine Strategie festgelegt ist, wenn calculateShipping aufgerufen wird, geben Sie 0.0 zurück.

  • main.cpp: Demonstrieren Sie das Wechseln von Strategien zur Laufzeit.

    Lesen Sie zwei Eingaben ein:

    1. Paketgewicht (double)
    2. Versandmethode: ground, air oder express

    Erstellen Sie einen ShippingService und setzen Sie die entsprechende Strategie basierend auf der Eingabemethode. Berechnen und geben Sie die Versandkosten aus.

    Wechseln Sie dann zu einer anderen Strategie (verwenden Sie air, wenn die Eingabe nicht air war, andernfalls verwenden Sie ground) und berechnen Sie die Kosten erneut für dasselbe Gewicht. Dies demonstriert die Stärke des Austauschens von Strategien zur Laufzeit.

    Geben Sie jeden Kostenwert in einer eigenen Zeile mit genau einer Dezimalstelle aus, vorangestellt durch den Methodennamen:

    [Method]: $[cost]

Zum Beispiel bei den Eingaben 5.0 und ground:

Ground: $7.5
Air: $20.0

Bei den Eingaben 3.0 und express:

Express: $29.5
Air: $12.0

Bei den Eingaben 10.0 und air:

Air: $40.0
Ground: $15.0

Beachten Sie, dass der ShippingService die Details der einzelnen Preisalgorithmen nicht kennen muss – er delegiert einfach an die aktuell eingestellte Strategie. Sie können problemlos neue Versandmethoden (wie Drohnenlieferung oder Same-Day) hinzufügen, ohne die Service-Klasse überhaupt zu ändern.

Spickzettel

Das Strategy-Muster (Strategiemuster) definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Dies ermöglicht es, das Verhalten eines Objekts zur Laufzeit zu ändern, ohne dessen Code zu modifizieren.

Das Muster besteht aus drei Teilen:

  • Strategy-Interface: Eine abstrakte Klasse, die die Algorithmus-Methode deklariert
  • Konkrete Strategien: Klassen, die verschiedene Variationen des Algorithmus implementieren
  • Kontext: Eine Klasse, die eine Strategie verwendet und zwischen ihnen wechseln kann
// Strategy interface
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// Concrete strategies
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// Context
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

Der Kontext weiß nicht, welche konkrete Strategie er verwendet – er ruft lediglich die Interface-Methode auf. Strategien können zur Laufzeit über eine Setter-Methode ausgetauscht werden:

ShoppingCart cart;

cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);

cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);

Wann man es verwendet: Verwenden Sie das Strategy-Muster, wenn Sie mehrere Algorithmen für eine bestimmte Aufgabe haben und dynamisch zwischen ihnen wechseln möchten, oder wenn Sie bedingte Anweisungen zur Auswahl des Verhaltens vermeiden wollen.

Probier es selbst

#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"

int main() {
    double weight;
    std::string method;
    
    std::cin >> weight;
    std::cin >> method;
    
    // Ausgabe auf 1 Dezimalstelle festlegen
    std::cout << std::fixed << std::setprecision(1);
    
    ShippingService service;
    
    // TODO: Basierend auf der Eingabemethode ("ground", "air" oder "express"):
    // 1. Die entsprechende Strategie für den Service festlegen
    // 2. Die Kosten berechnen und im Format "[Method]: $[cost]" ausgeben
    
    // TODO: Zu einer anderen Strategie wechseln:
    // - Wenn die Eingabe "air" war, zu GroundShipping wechseln
    // - Andernfalls zu AirShipping wechseln
    // Die neuen Kosten berechnen und ausgeben
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung