Strategy Pattern
Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 95 of 104.
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This lets you change an object's behavior at runtime without modifying its code - the algorithm varies independently from the clients that use it.
The pattern consists of three parts: a Strategy interface declaring the algorithm method, Concrete Strategies implementing different variations, and a Context that uses a strategy:
#include <iostream>
#include <memory>
// 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);
}
};
int main() {
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);
}The ShoppingCart doesn't know which payment method it's using - it just calls pay() on whatever strategy is set. You can swap strategies at runtime with setPaymentMethod(), making the system flexible and easy to extend with new payment options.
Use Strategy when you have multiple algorithms for a specific task and want to switch between them dynamically, or when you want to avoid conditional statements for selecting behavior.
Challenge
EasyLet's build a Shipping Calculator that uses the Strategy pattern to calculate delivery costs based on different shipping methods. This is a practical scenario where you need to swap algorithms at runtime—the same package might be shipped via ground, air, or express, each with its own pricing logic.
You'll organize your code across three files:
ShippingStrategy.h: Define your strategy interface and concrete shipping strategies.Create an abstract
ShippingStrategyclass with a pure virtual methodcalculateCost(double weight)that returns the shipping cost as a double, along with a virtual destructor.Then implement three concrete strategies:
GroundShipping— costs1.5per unit of weight (weight * 1.5)AirShipping— costs4.0per unit of weight (weight * 4.0)ExpressShipping— costs6.5per unit of weight plus a flat fee of10.0(weight * 6.5 + 10.0)
ShippingService.h: Create the context class that uses a shipping strategy.Your
ShippingServiceclass should hold astd::unique_ptr<ShippingStrategy>as a private member. Implement:- A
setStrategy(std::unique_ptr<ShippingStrategy> strategy)method to change the shipping method - A
calculateShipping(double weight)method that uses the current strategy to compute and return the cost
If no strategy is set when
calculateShippingis called, return0.0.- A
main.cpp: Demonstrate switching strategies at runtime.Read two inputs:
- Package weight (double)
- Shipping method:
ground,air, orexpress
Create a
ShippingServiceand set the appropriate strategy based on the input method. Calculate and print the shipping cost.Then switch to a different strategy (use
airif the input wasn'tair, otherwise useground) and calculate the cost again for the same weight. This demonstrates the power of swapping strategies at runtime.Print each cost on its own line with exactly one decimal place, prefixed with the method name:
[Method]: $[cost]
For example, with inputs 5.0 and ground:
Ground: $7.5
Air: $20.0With inputs 3.0 and express:
Express: $29.5
Air: $12.0With inputs 10.0 and air:
Air: $40.0
Ground: $15.0Notice how the ShippingService doesn't need to know the details of each pricing algorithm—it simply delegates to whatever strategy is currently set. You can easily add new shipping methods (like drone delivery or same-day) without modifying the service class at all.
Cheat sheet
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This allows changing an object's behavior at runtime without modifying its code.
The pattern consists of three parts:
- Strategy interface: An abstract class declaring the algorithm method
- Concrete Strategies: Classes implementing different algorithm variations
- Context: A class that uses a strategy and can switch between them
// 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);
}
};The context doesn't know which concrete strategy it's using—it just calls the interface method. Strategies can be swapped at runtime using a setter method:
ShoppingCart cart;
cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);
cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);When to use: Use Strategy when you have multiple algorithms for a specific task and want to switch between them dynamically, or when you want to avoid conditional statements for selecting behavior.
Try it yourself
#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;
// Set output to 1 decimal place
std::cout << std::fixed << std::setprecision(1);
ShippingService service;
// TODO: Based on the input method ("ground", "air", or "express"):
// 1. Set the appropriate strategy on the service
// 2. Calculate and print the cost in format: "[Method]: $[cost]"
// TODO: Switch to a different strategy:
// - If input was "air", switch to GroundShipping
// - Otherwise, switch to AirShipping
// Calculate and print the new cost
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy10STL Overview
STL Overview & PhilosophySTL ContainersIteratorsSTL AlgorithmsFunctors & Lambda ExpressionsRecap - Word Frequency13Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory & Abstract FactoryBuilder PatternObserver PatternStrategy Pattern2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container