C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
138: The Strategy Pattern
I once worked with a junior dev on a simulation project where we had a Vehicle class. At first, it was simple: the vehicle moved in a straight line. But then the requirements shifted. We needed drones that flew in arcs, submarines that navigated 3D currents, and land vehicles that followed road networks. The developer's first instinct was to add an enum VehicleType and a massive switch statement inside the move() method. Every time a new movement type was requested, he had to open that same file, add a case to the switch, and re-test every single vehicle type to make sure he hadn't accidentally broken the drones while fixing the submarines.
It was a maintenance nightmare. The Vehicle class had become a "God Object," knowing too much about the specifics of every single movement algorithm in the game. This is exactly where the Strategy Pattern saves your sanity. Instead of one class trying to handle every possible variation of a behavior, you encapsulate each behavior into its own class. You stop asking the object what it is and start telling it how to behave.
Decoupling Behavior from State
The core idea is to define a family of algorithms, encapsulate each one, and make them interchangeable. In C++, we do this by creating an abstract base class (the "Strategy") that defines a common interface. The "Context"—the object actually using the strategy—holds a pointer to this base class. It doesn't know or care which concrete implementation it's using; it just calls a virtual function.
#include <iostream>
#include <memory>
#include <string>
// The Strategy Interface
class MovementStrategy {
public:
virtual ~MovementStrategy() = default;
virtual void move() const = 0;
};
// Concrete Strategy A
class FlyBehavior : public MovementStrategy {
public:
void move() const override {
std::cout <"Swooping through the air with style!\n";
}
};
// Concrete Strategy B
class SwimBehavior : public MovementStrategy {
public:
void move() const override {
std::cout <"Gliding silently through the deep ocean.\n";
}
};
// The Context
class Vehicle {
private:
std::unique_ptr<MovementStrategy> movementStyle;
public:
void setMovementStrategy(std::unique_ptr<MovementStrategy> strategy) {
movementStyle = std::move(strategy);
}
void executeMove() {
if (movementStyle) {
movementStyle->move();
} else {
std::cout <"Vehicle is stationary.\n";
}
}
};
By using std::unique_ptr, we ensure that the Vehicle owns its strategy and cleans it up properly. Notice how the Vehicle class is now completely agnostic about how movement actually happens. If you want to add a TeleportBehavior, you don't touch the Vehicle class at all; you just create a new class that inherits from MovementStrategy.
Swapping Logic at Runtime
The real magic happens when you realize that because the strategy is just a pointer, you can change it while the program is running. In a traditional inheritance hierarchy, a Drone is always a Drone. But with the Strategy Pattern, a Vehicle can start as a land rover and, upon hitting water, switch its strategy to SwimBehavior instantly.
I often see people confuse this with the State Pattern. They're structurally similar, but the intent is different. State is about what the object is (and how that changes its behavior), while Strategy is about how the object performs a specific task. When you're designing your system, ask yourself: "Am I trying to handle different versions of the same action?" If the answer is yes, reach for the Strategy Pattern.
One small tip from the trenches: be careful with the overhead of virtual function calls if this logic is being executed millions of times per second in a tight loop. In most business logic or high-level game AI, the cost is negligible, but in a physics engine, you might look into std::variant and std::visit for a "static" version of the Strategy Pattern that avoids the vtable hit.
📋 Practical Task
Implementation: Dynamic Combat AI for a Tower Defense Game
You are building a Tower Defense game. Your enemy units need different "Attack Strategies" based on their distance from the tower. Instead of using a complex if/else chain inside the Enemy class, implement the Strategy Pattern to handle their combat logic.
- Create an abstract base class
AttackStrategywith a pure virtual methodexecuteAttack(). - Implement three concrete strategies:
MeleeAttack: Prints "Attacking with a sword!"RangedAttack: Prints "Firing an arrow from a distance!"MagicAttack: Prints "Casting a fireball!"
- Create an
Enemyclass that holds astd::unique_ptr<AttackStrategy>. - Implement a method
changeStrategy(std::unique_ptr<AttackStrategy> newStrategy)that allows the enemy to switch their attack style at runtime. - In your
mainfunction, instantiate anEnemy, make them perform aMeleeAttack, then switch them to aMagicAttackand execute it again.
There are no comments for now.