Skip to main content

Command Palette

Search for a command to run...

Strategy Design Pattern

Updated
•4 min read•View as Markdown
N

Software Engineer II currently working at Akamai Technologies. Full Stack Web Development and DevOps enthusiast.

Definition : Formally, strategy design pattern can be defined as a design pattern that defines a family of algorithms and put them into separate classes so that they can be changed at run-time.

Lets understand strategy design pattern with help of an example :

We have a creature class, that acts as a base class for defining creatures like human , bird, animal etc. Lets consider that creature class has walk, talk, appearance member functions. The UML diagram will look like :

  • Creature is an abstract base class and have only declaration of member functions walk, talk and appearance.

  • Human, Insects and Animal are the the concrete implementations inherited from Creature

  • The inherited classes have defined the member functions. Each child class can have it’s own version of member function definition.

Suppose, now we have to create another creature : bird having a fly member function along with the existing member functions.

Option 1 : We can add fly in the base class, so that all the child classes have inherited fly member function.

What’s wrong with above approach ?

  • Human , Insects don’t need fly method. Still they will inherit fly method, which is incorrect.

    • Humans cannot fly.

    • Some insects can fly but lets assume that insects don’t fly.

  • Although, we can define fly method to throw error inside Human and Insects class. This is acceptable but in the first place itself, we should not have inherited the fly method.

Option 2 : We can define another abstract class flyable_creature that inherits from Creature class. The flyable_creature class will inherit all the member functions of creature class and also add another member function fly. Now, we can inherit bird from flyable_creature class.

  • All the objects now only have relevant methods.

  • This approach is correct but think about the scenario if more creatures are added with different behaviors.

    • Drawbacks:

      • The inheritance tree will keep growing.

      • Whenever a new creature is created with some different member function, the class structure needs to be modified.

      • Breaking open close principle.

      • Violating DRY principle(do not repeat yourself).

How does Strategy Design pattern overcome these drawbacks?

From the definition, we know that strategy design pattern is a design pattern that defines a family of algorithms and put them into separate classes so that they can be changed at run-time.

  • We can consider walk , talk , fly and appearance as different algorithms and put them into separate classes.

  • The Creature class will have reference to the interfaces of these algorithms.

  • The concrete class is determined during run-time.

  • Strategies have been defined : Talkable, Walkable, Flyable and Appearable.

  • Each strategy is an interface (interface is implemented using abstract class in python).

  • The concrete implementations can extend their respective interface and define the abstract method.

  • Creature is coded against the interfaces and not against the concrete classes.

  • The actual walk, talk, fly and appearance method is determined at run-time.

Implementation of Strategy Design Pattern in Python

from abc import ABC, abstractmethod

# Interfaces declaring different Strategies
class Talkable(ABC):
    @abstractmethod
    def talk(self):
        raise NotImplementedError

class Walkable(ABC):
    @abstractmethod
    def walk(self):
        raise NotImplementedError

class Flyable(ABC):
    @abstractmethod
    def fly(self):
        raise NotImplementedError

class Apperable(ABC):
    @abstractmethod
    def appearance(self):
        raise NotImplementedError

# Concrete implementation of these strategies
class NoTalk(Talkable):
    def talk(self):
        print("I cannot talk")

class CanTalk(Talkable):
    def talk(self):
        print("I can talk")

class NoWalk(Walkable):
    def walk(self):
        print("I cannot walk")

class CanWalk(Walkable):
    def walk(self):
        print("I can walk")

class NoFly(Flyable):
    def fly(self):
        print("I cannot fly")

class CanFly(Flyable):
    def fly(self):
        print("I can fly")

class NoApperance(Apperable):
    def appearance(self):
        print("I donot appear")

class CanAppear(Apperable):
    def appearance(self):
        print("I can appear")

# Define the client (in this case creature)
class Creature:
    def __init__(self, t : Talkable, w : Walkable, f : Flyable, a : Apperable):
        self.talkable = t
        self.walkable = w
        self.flyable = f
        self.apperable = a

    def walk(self):
        self.walkable.walk()

    def talk(self):
        self.talkable.talk()

    def fly(self):
        self.flyable.fly()

    def appearance(self):
        self.apperable.appearance()

if __name__ == "__main__":
    human = Creature(CanTalk(), CanWalk(), NoFly(), CanAppear())
    human.talk()
    human.walk()
    human.fly()
    human.appearance()

    bacteria = Creature(NoTalk(), CanWalk(), NoFly(), NoApperance())
    bacteria.talk()
    bacteria.walk()
    bacteria.fly()
    bacteria.appearance()

Output:

I can talk
I can walk
I cannot fly
I can appear
I cannot talk
I can walk
I cannot fly
I donot appear

Conclusion

  1. Encapsulate what varies and keep it separate from what remains same.

  2. Solution to inheritance is not more inheritance.

  3. Composition should be favored over inheritance.

  4. Code to interface and not concrete implementation.

  5. Do not repeat yourself.