Skip to Content
Course content

138: The Strategy Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 AttackStrategy with a pure virtual method executeAttack().
  • 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 Enemy class that holds a std::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 main function, instantiate an Enemy, make them perform a MeleeAttack, then switch them to a MagicAttack and execute it again.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.