Strategie-Muster
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 95 von 104.
Das Strategy-Muster definiert eine Familie von Algorithmen, kapselt jeden einzelnen und macht sie austauschbar. Dadurch kannst du das Verhalten eines Objekts zur Laufzeit ändern, ohne seinen Code zu modifizieren – der Algorithmus variiert unabhängig von den Clients, die ihn verwenden.
Das Muster besteht aus drei Teilen: einer Strategy-Schnittstelle, die die Algorithmusmethode deklariert, konkreten Strategien, die verschiedene Varianten 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 festgelegte Strategie auf. Du kannst Strategien zur Laufzeit mit setPaymentMethod() austauschen, wodurch das System flexibel und einfach um neue Zahlungsoptionen erweiterbar wird.
Verwende Strategy, wenn du mehrere Algorithmen für eine bestimmte Aufgabe hast und dynamisch zwischen ihnen wechseln möchtest oder wenn du bedingte Anweisungen zur Auswahl des Verhaltens vermeiden möchtest.
Aufgabe
EinfachErstellen wir einen Versandkostenrechner, der das Strategy-Muster verwendet, um Lieferkosten anhand verschiedener Versandmethoden zu berechnen. Dies ist ein praktisches Szenario, in dem Algorithmen zur Laufzeit ausgetauscht werden müssen: Dasselbe Paket könnte auf dem Landweg, per Luftfracht oder als Expresssendung verschickt werden, wobei jede Methode ihre eigene Preislogik hat.
Du organisierst deinen Code auf drei Dateien verteilt:
ShippingStrategy.h: Definiere deine Strategie-Schnittstelle und konkrete Versandstrategien.Erstelle eine abstrakte Klasse
ShippingStrategymit einer rein virtuellen MethodecalculateCost(double weight), die die Versandkosten als double zurückgibt, sowie einem virtuellen Destruktor.Implementiere anschließend drei konkrete Strategien:
GroundShipping: kostet1.5pro Gewichtseinheit (weight * 1.5)AirShipping: kostet4.0pro Gewichtseinheit (weight * 4.0)ExpressShipping: kostet6.5pro Gewichtseinheit plus eine Pauschale von10.0(weight * 6.5 + 10.0)
ShippingService.h: Erstelle die Kontextklasse, die eine Versandstrategie verwendet.Deine Klasse
ShippingServicesollte einstd::unique_ptr<ShippingStrategy>als privates Mitglied enthalten. Implementiere:- 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 beim Aufruf von
calculateShippingkeine Strategie festgelegt ist, gib0.0zurück.- Eine Methode
main.cpp: Zeige das Umschalten von Strategien zur Laufzeit.Lies zwei Eingaben ein:
- Paketgewicht (double)
- Versandmethode:
ground,airoderexpress
Erstelle einen
ShippingServiceund lege anhand der eingegebenen Methode die passende Strategie fest. Berechne die Versandkosten und gib sie aus.Wechsle anschließend zu einer anderen Strategie (verwende
air, wenn die Eingabe nichtairwar, andernfalls verwendeground) und berechne die Kosten für dasselbe Gewicht erneut. Dies demonstriert die Vorteile des Austauschs von Strategien zur Laufzeit.Gib jede Kostenangabe in einer eigenen Zeile mit genau einer Dezimalstelle aus, mit dem Namen der Methode als Präfix:
[Method]: $[cost]
Zum Beispiel bei den Eingaben 5.0 und ground:
Ground: $7.5
Air: $20.0Bei den Eingaben 3.0 und express:
Express: $29.5
Air: $12.0Bei den Eingaben 10.0 und air:
Air: $40.0
Ground: $15.0Beachte, dass ShippingService die Details der einzelnen Preisalgorithmen nicht kennen muss. Die Klasse delegiert einfach an die Strategie, die aktuell festgelegt ist. Du kannst problemlos neue Versandmethoden hinzufügen (zum Beispiel Drohnenlieferung oder Lieferung am selben Tag), ohne die Serviceklasse überhaupt zu ändern.
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;
// Setze die Ausgabe auf 1 Dezimalstelle
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: Basierend auf der Eingabemethode ("ground", "air" oder "express"):
// 1. Setze die passende Strategie auf dem Service
// 2. Berechne und gib die Kosten im Format aus: "[Method]: $[cost]"
// TODO: Wechsle zu einer anderen Strategie:
// - Wenn die Eingabe "air" war, wechsle zu GroundShipping
// - Andernfalls wechsle zu AirShipping
// Berechne und gib die neuen Kosten aus
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie10STL-Übersicht
STL-Übersicht & PhilosophieSTL-ContainerIteratorenSTL-AlgorithmenFunktoren & Lambda-AusdrückeRückblick – Worthäufigkeit13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory und Abstract FactoryBuilder-MusterBeobachter-MusterStrategie-Muster2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler