DEV Community

Rajender Singh Banoula
Rajender Singh Banoula

Posted on

Strategy design pattern

The Tale of Ducks with Special Abilities: Understanding the Strategy Pattern

Imagine you're in charge of a pond where different types of ducks live. Each duck species has unique abilities. For example, the Wood Duck can fly and quack, while the Pekin Duck can quack but doesn’t fly much.

Initially, you might think of organizing the ducks using a class hierarchy where all ducks inherit common behaviors like flying and quacking. But soon, you realize this approach creates problems, especially when certain ducks change behavior or don't fit perfectly into the hierarchy. This is where the Strategy Pattern comes to the rescue.

Image description


The Strategy Pattern: What Is It?

The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one, and makes them interchangeable.

This means that instead of hardcoding a behavior directly into the duck class, we break it out into separate classes (strategies), making it easy to change or replace behaviors without touching the core duck code.


Encapsulating Behavior into Strategies (Defining a Set of Algorithms)

In our duck example, flying and quacking are the behaviors (algorithms) that can vary between different ducks. With the Strategy Pattern, we create separate classes for each unique behavior, and we can dynamically assign them to different duck species.

Image description

These behaviors (algorithms) include:

  1. Flying with wings (for ducks like the Wood Duck).
  2. Not being able to fly (for ducks like the Pekin Duck).
  3. Quacking loudly (for both Wood Duck and Pekin Duck).
  4. Staying silent (for ducks that don’t quack).

Here’s how we define these algorithms:

Flying Behavior Interface: This defines a general way for ducks to fly.

class FlyBehavior {
 public:
     virtual void fly() = 0;
};
Enter fullscreen mode Exit fullscreen mode

Quacking Behavior Interface: This defines how ducks can quack.

class QuackBehavior { 
 public:
     virtual void quack() = 0;
};
Enter fullscreen mode Exit fullscreen mode

Next, we create concrete strategies (algorithms) that represent the specific ways ducks can fly or quack.

Flying Behaviors (Set of Algorithms):

We define two types of flying behaviors: one for ducks that can fly and one for ducks that can’t.

class FlyWithWings : public FlyBehavior {
 public:
     void fly() override {
         cout << "Flying with wings!" << endl;
     }
};
class NoFly : public FlyBehavior {
 public:
     void fly() override {
         cout << "I can't fly!" << endl;
     }
};
Enter fullscreen mode Exit fullscreen mode

Quacking Behaviors (Set of Algorithms):

Similarly, we define behaviors for quacking loudly or remaining silent.

class QuackLoudly : public QuackBehavior {
 public:
     void quack() override {
         cout << "Quacking loudly!" << endl;
     }
};  
class SilentQuack : public QuackBehavior {
 public:
     void quack() override {
         cout << "..." << endl;
     }
};
Enter fullscreen mode Exit fullscreen mode

Composition Over Inheritance: Why It’s Better

Instead of hardcoding the flying and quacking behaviors into the duck classes through inheritance, we now compose the behaviors using strategy objects. This means we can easily swap behaviors at runtime or extend our system without touching the base duck class.

Here’s the key difference:

  • Inheritance locks you into a rigid class hierarchy. If a duck changes its ability to fly or quack, you’d have to rewrite the class or introduce conditions, leading to messy and hard-to-maintain code.

  • Composition lets you plug in and change behaviors dynamically without altering the rest of the system. The duck class stays clean, and behaviors are modular.


Putting It All Together: The Duck Class

Now, let’s integrate this into the main Duck class. Each duck will have its own flying and quacking behavior, which can be set dynamically.

class Duck {
 protected:
     FlyBehavior* flyBehavior;
     QuackBehavior* quackBehavior;
 public:
     virtual void swim() {
         cout << "Swimming..." << endl;
     }
     void performFly() {
         flyBehavior->fly();
     }
     void performQuack() {
         quackBehavior->quack();
     } 
     virtual void display() = 0; // Set new behaviors at runtime 
     void setFlyBehavior(FlyBehavior* fb) {
         flyBehavior = fb;
     }
     void setQuackBehavior(QuackBehavior* qb) {         
         quackBehavior = qb;
     }
};
Enter fullscreen mode Exit fullscreen mode

Defining Specific Ducks

Let’s create our specific duck species. We’ll start with the Wood Duck (which can fly and quack) and the Pekin Duck (which quacks but doesn’t fly).

class WoodDuck : public Duck {
 public:
     WoodDuck() {
         flyBehavior = new FlyWithWings();
         quackBehavior = new QuackLoudly();
     } 
     void display() override {
         cout << "I am a Wood Duck!" << endl;
     }
};

class PekinDuck : public Duck {
 public:
     PekinDuck() {
         flyBehavior = new NoFly();
         quackBehavior = new QuackLoudly();
     } 
     void display() override {
         cout << "I am a Pekin Duck!" << endl;
     }
};
Enter fullscreen mode Exit fullscreen mode

Changing Behavior Dynamically

With the Strategy Pattern, you can change a duck’s behavior at runtime. Let’s say you have a Pekin Duck that suddenly learns to fly. You can dynamically swap its behavior without changing its class:

int main() {
     Duck* woodDuck = new WoodDuck();
     woodDuck->performFly();     // Output: Flying with wings!     
     woodDuck->performQuack();   // Output: Quacking loudly!

     Duck* pekinDuck = new PekinDuck();
     pekinDuck->performFly();    // Output: I can't fly!     
     pekinDuck->performQuack();  // Output: Quacking loudly!

     // Change Pekin Duck's flying behavior at runtime
     pekinDuck->setFlyBehavior(new FlyWithWings());     
     pekinDuck->performFly();    // Output: Flying with wings! 
}
Enter fullscreen mode Exit fullscreen mode

Summary: Why Use the Strategy Pattern?

  • Encapsulation of behaviors: The ability to fly and quack are now encapsulated into their own classes, making them easy to manage, extend, or modify.
  • Interchangeable algorithms: Different flying or quacking strategies (algorithms) can be swapped out easily, even at runtime.
  • Flexible and reusable code: You can create new ducks with different combinations of behaviors without changing the core duck code. The same flying or quacking behaviors can be reused across different ducks.

By applying the Strategy Pattern, you make your system adaptable and easier to maintain. The key idea is to encapsulate what changes (behavior) and use composition to allow flexibility, making your duck pond a place where behaviors can evolve and adapt without a ripple of complexity! 🦆✨

Top comments (0)