Modèle Strategy
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 95 sur 104.
Le pattern Strategy définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Cela vous permet de modifier le comportement d’un objet à l’exécution sans modifier son code : l’algorithme varie indépendamment des clients qui l’utilisent.
Le modèle se compose de trois parties : une interface Strategy déclarant la méthode de l’algorithme, des stratégies concrètes implémentant différentes variantes, et un contexte qui utilise une stratégie :
#include <iostream>
#include <memory>
// Interface Strategy
class PaymentStrategy {
public:
virtual void pay(int amount) = 0;
virtual ~PaymentStrategy() = default;
};
// Stratégies concrètes
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";
}
};
// Contexte
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);
}Le ShoppingCart ne sait pas quelle méthode de paiement il utilise : il appelle simplement pay() sur la stratégie configurée. Vous pouvez remplacer les stratégies à l’exécution avec setPaymentMethod(), ce qui rend le système flexible et facile à étendre avec de nouvelles options de paiement.
Utilisez Strategy lorsque vous disposez de plusieurs algorithmes pour une tâche spécifique et que vous souhaitez basculer dynamiquement entre eux, ou lorsque vous voulez éviter les instructions conditionnelles pour sélectionner un comportement.
Défi
FacileConstruisons un calculateur de frais d'expédition qui utilise le modèle Strategy pour calculer les coûts de livraison en fonction de différentes méthodes d'expédition. Il s'agit d'un scénario pratique dans lequel vous devez remplacer des algorithmes à l'exécution : le même colis peut être expédié par voie terrestre, aérienne ou express, chacune de ces méthodes ayant sa propre logique tarifaire.
Vous organiserez votre code dans trois fichiers :
ShippingStrategy.h: définir votre interface de stratégie et les stratégies d'expédition concrètes.Créez une classe abstraite
ShippingStrategyavec une méthode virtuelle purecalculateCost(double weight)qui renvoie le coût d'expédition sous forme de double, ainsi qu'un destructeur virtuel.Implémentez ensuite trois stratégies concrètes :
GroundShipping: coûte1.5par unité de poids (weight * 1.5)AirShipping: coûte4.0par unité de poids (weight * 4.0)ExpressShipping: coûte6.5par unité de poids, plus des frais fixes de10.0(weight * 6.5 + 10.0)
ShippingService.h: créer la classe de contexte qui utilise une stratégie d'expédition.Votre classe
ShippingServicedoit contenir unstd::unique_ptr<ShippingStrategy>en tant que membre privé. Implémentez :- Une méthode
setStrategy(std::unique_ptr<ShippingStrategy> strategy)pour modifier la méthode d'expédition - Une méthode
calculateShipping(double weight)qui utilise la stratégie actuelle pour calculer et renvoyer le coût
Si aucune stratégie n'est définie lorsque
calculateShippingest appelée, renvoyez0.0.- Une méthode
main.cpp: montrer le changement de stratégie à l'exécution.Lisez deux entrées :
- Poids du colis (double)
- Méthode d'expédition :
ground,airouexpress
Créez un
ShippingServiceet définissez la stratégie appropriée en fonction de la méthode saisie. Calculez et affichez le coût d'expédition.Passez ensuite à une autre stratégie (utilisez
airsi l'entrée n'était pasair, sinon utilisezground) et calculez à nouveau le coût pour le même poids. Cela démontre la puissance du remplacement des stratégies à l'exécution.Affichez chaque coût sur sa propre ligne avec exactement une décimale, précédé du nom de la méthode :
[Method]: $[cost]
Par exemple, avec les entrées 5.0 et ground :
Ground: $7.5
Air: $20.0Avec les entrées 3.0 et express :
Express: $29.5
Air: $12.0Avec les entrées 10.0 et air :
Air: $40.0
Ground: $15.0Remarquez que ShippingService n'a pas besoin de connaître les détails de chaque algorithme tarifaire. Il délègue simplement à la stratégie actuellement définie. Vous pouvez facilement ajouter de nouvelles méthodes d'expédition (comme la livraison par drone ou la livraison le jour même) sans modifier la classe de service.
Essayez vous-même
#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;
// Définir la sortie à 1 décimale
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: En fonction de la méthode d'entrée ("ground", "air", or "express") :
// 1. Définir la stratégie appropriée sur le service
// 2. Calculer et afficher le coût au format : "[Method]: $[cost]"
// TODO: Passer à une stratégie différente :
// - Si l'entrée était "air", passer à GroundShipping
// - Sinon, passer à AirShipping
// Calculer et afficher le nouveau coût
return 0;
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d’accès en C++Spécificateurs d’accès en profondeurMasquage de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l’opérateur d’affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif – Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne