The Strategy Pattern and the Duck That Couldn't Share a Trick

Two ducks need the exact same flying trick but are not related in the class tree, and why pulling that trick into its own small object fixes it for good.

September 8, 20265 min read1 / 19

Here's the idea in one sentence: if two ducks need to fly the exact same special way, but they aren't in the same family tree, put that flying trick in its own little box instead of forcing it into the tree, and hand that box to any duck that needs it.

A Duck Class That Grew Too Many Cousins

Picture a small game with ducks in it. Every duck can quack, and every duck can be shown on screen. That feels like a normal job for one shared parent class, so we start with Duck, and let every specific kind of duck inherit from it.

class Duck { public: virtual ~Duck() = default; void quack() { std::cout << "Quack!\n"; } virtual void display() = 0; }; class WildDuck : public Duck { public: void display() override { std::cout << "A wild duck.\n"; } }; class CityDuck : public Duck { public: void display() override { std::cout << "A city duck.\n"; } };

Both ducks quack the exact same way. That code only needs to live once, in the parent class. That's inheritance doing its normal job: sharing code downward, from parent to child.

Then a new duck shows up. It's a RubberDuck, a plastic toy. Someone had already added a fly() method to Duck, since real ducks fly. A rubber duck can't fly, so RubberDuck has to override fly() and make it do nothing.

class Duck { public: virtual ~Duck() = default; void quack() { std::cout << "Quack!\n"; } virtual void fly() { std::cout << "Flying...\n"; } virtual void display() = 0; }; class RubberDuck : public Duck { public: void fly() override { /* rubber ducks don't fly */ } void display() override { std::cout << "A rubber duck.\n"; } };

Still fine. But now two more ducks show up: a MountainDuck and a CloudDuck. Both fly in the exact same special way, and that way is different from the normal Duck::fly(). Where does that shared code go?

MountainDuck and CloudDuck aren't parent and child. They're cousins. In a plain family tree, the only place their shared flying trick can live is copy-pasted into both classes.

class MountainDuck : public Duck { public: void fly() override { std::cout << "Soaring on mountain winds...\n"; } void display() override { std::cout << "A mountain duck.\n"; } }; class CloudDuck : public Duck { public: void fly() override { std::cout << "Soaring on mountain winds...\n"; } // same code, copy-pasted void display() override { std::cout << "A cloud duck.\n"; } };

Inheritance Only Shares Code One Way

Think of a family tree. A parent can pass something down to a child. A parent can never hand something sideways to a cousin.

A family tree only shares tricks one way, down from parent to child.

The moment two ducks that aren't related need that exact same trick, the tree simply has no branch connecting them.

Copy-paste is the only option left. And copy-paste means fixing the same bug in two places, every single time.

The next post pulls that shared flying trick out of the family tree and into its own little box, so it only ever has to live in one place.

The Essentials

  1. A family tree can only pass a trick one way: down, from parent to child. Never sideways, between cousins.
  2. Two ducks that aren't related but need the same trick have only one option left: copy-paste it into both.
  3. That's the exact problem the strategy pattern was built to solve.