While you are developing software applications, you might run into some problems implementing the feature you need. Software design patterns are typical solutions for some of these commonly occurring problems while developing software applications using Object-oriented design. In this article, let's learn about one of the popular design patterns, Strategy pattern, and its implementation in Python.
Introduction
Before diving into the strategy pattern, you should be familiar with some of the basics concepts of Object-Oriented Programming (OOP). The entire concept of design patterns revolves around classes and objects. The design patterns are more high-level solutions for commonly occurring problems. They are like the blueprint to solve a specific problem. They are not confined to a single programming language. You can use design patterns in any programming language that supports object-oriented programming; the process will be the same while the syntax changes. There are several types of design patterns, including Creational, Structural, and Behavioral patterns. Creational patterns are about different ways to create objects that increase the flexibility of our code. Structural patterns are about relations between the objects, making larger structures flexible using objects and classes. Behavioral patterns are about effective communications and interactions between objects.
Strategy
Strategy Pattern is a design pattern that enables our application to select algorithms at runtime, making our application flexible. The original book on design patterns written by GoF states that "Strategy pattern intends to define a family of algorithms, encapsulates each one, and make them interchangeable." More specifically, it lets you define a set of algorithms that are interchangeable according to some factors at runtime. Strategy Pattern falls under the category of behavioral design patterns as it enables an algorithm's behavior to be selected at runtime.
Usage
While developing software applications, you may have a few alternatives to accomplish something in your code. Depending on your client choices, data sources, or other factors, you want to do something different without changing the code. You often tend to define algorithms using conditional statements for different situations in the main class of the code. But it is not an elegant way of writing better code. It makes the main class of your code quite long, and it becomes too hard to maintain the application.
In situations like these, the strategy pattern is an ideal solution. The strategy pattern suggests you define classes, called strategies, for your algorithms of different situations. The strategy is referenced inside the main class, called context, and the code works according to that situation. The context does not select an appropriate strategy for the case. Instead, the client passes the desired strategy to the context.
For example, if you have a chess application, you can select the difficulty level between easy, medium, or hard. The computer chooses an algorithm according to the level you choose. It is one of the best examples where the strategy pattern is used.
Strategy pattern follows the Open/close principle; a software application is open for extension but closed for modification. It means you can add any number of additional strategies without modifying the main class. It makes your code more flexible and easy to maintain.
UML Diagrams
The following is the UML diagram of the Strategy pattern.
- Context — It is the primary class of our application. It maintains a reference to one of the concrete strategies.
- Strategy — Strategy interface is common to all supported strategies. Context can communicate with other strategies only through the strategy interface.
- ConcreteStrategies — These are the classes that implement the algorithm using the Strategy interface.
Implementation
Let's see the step-by-step process of implementing a strategy pattern.
- You should first identify algorithms you want to execute as concrete strategies in the primary class.
- Define the context (primary class) and add a reference to the strategy, a method to set the strategy, and another method to execute the strategy. You may also define a default strategy to switch between strategies only if they do not like the default one.
## context - the primary class class Context: strategy: Strategy ## the strategy interface def setStrategy(self, strategy: Strategy = None) -> None: if strategy is not None: self.strategy = strategy else: self.strategy = Default() def executeStrategy(self) -> str: print(self.strategy.execute())
First, we define the
strategy
field for storing a reference to a strategy object, and two methods, setStrategy
and executeStrategy
. The setStrategy
sets the strategy selected if a user selects an option, or else the default
one.- Define the Strategy Interface, which is common to all the concrete strategies. The
interface has an abstract method that you can alter in concrete strategies.Strategy
from abc import ABC, abstractmethod ## Strategy interface class Strategy(ABC): @abstractmethod def execute(self) -> str: pass
- Define the concrete strategies which should implement the
interface. These concrete strategies must have a common method that overrides theStrategy
method of theexecute
interface.Strategy
## Concrete strategies class ConcreteStrategyA(Strategy): def execute(self) -> str: return "ConcreteStrategy A" class ConcreteStrategyB(Strategy): def execute(self) -> str: return "ConcreteStrategy B" class Default(Strategy): def execute(self) -> str: return "Default"
- Now, users can select the strategy they want at the runtime. Create an object of context and pass a concrete strategy.
## Example application appA = Context() appB = Context() appC = Context() ## selecting stratigies appA.setStrategy(ConcreteStrategyA()) appB.setStrategy(ConcreteStrategyB()) appC.setStrategy() ## sets to default strategy ## each object below execute different strategy with same method appA.executeStrategy() appB.executeStrategy() appC.executeStrategy()
The output of the above code will be as follow:
ConcreteStrategy A ConcreteStrategy B Default
If you want to use another strategy, replace the ConcreteStrategy instance with the strategy you want. You can add a new concrete strategy without changing anything in the context.
Example
Let's design a rock paper scissors game using strategy pattern. You can select any strategy among rock, paper, scissors, and random to play against the computer. The below example code uses the strategy pattern to implement various strategies.
## Changing the strategy among Rock, Paper, Scissors, and Random import random from abc import ABC, abstractmethod ## Strategy interface class Strategy(ABC): @abstractmethod def selection(self) -> None: pass ## Concrete strategies class Rock(Strategy): ## actual application will have the algorithm instead this method def selection(self) -> str: return "Rock" class Paper(Strategy): def selection(self) -> str: return "Paper" class Scissors(Strategy): def selection(self) -> str: return "Scissors" class Random(Strategy): def selection(self) -> str: options = ["Rock", "Paper", "Scissors"] return random.choice(options) ## Context class class Game: strategy: Strategy def __init__(self, strategy: Strategy = None) -> None: if strategy is not None: self.strategy = strategy else: self.strategy = Random() def play(self, sec) -> None: s1 = self.strategy.selection() s2 = sec.strategy.selection() if s1 == s2: print("It's a tie") elif s1 == "Rock": if s2 == "Scissors": print("Player 1 wins!") else: print("Player 2 wins!") elif s1 == "Scissors": if s2 == "Paper": print("Player 1 wins!") else: print("Player 2 wins!") elif s1 == "Paper": if s2 == "Rock": print("Player 1 wins!") else: print("Player 2 wins!") ## Example application ## PLayer 1 can select his strategy player1 = Game(Paper()) # Player 2 gets to select player2 = Game(Rock()) # After the second player choice, we call the play method player1.play(player2)
According to the strategies chosen by the two players, the expected output would be:
Player 1 wins!
Test all the other cases of the game using all the other strategies. To add extra fun to the game, try creating two more strategies to the above example according to the Lizard-Spock expansion.
Conclusion
In this article, you have seen where and how to use the strategy pattern in your code. You can build flexible and maintainable software applications using the strategy pattern. You can switch between algorithms at runtime according to the user's decision without changing the code. But if your code only has a couple of algorithms, there is no need to use strategy. It just makes your code look complex with numerous classes and objects. The Strategy pattern can work as an alternative for conditional statements for selecting the behavior of the application. But the potential drawback of strategy pattern is that the users must know how strategies differ from each other to select what they need. So it would be best if you use the strategy pattern only when the variation in behavior of the application is relevant to the users. So try to make your software applications flexible using the strategy pattern.
About the author
Giridhar Talla
Creative Web Developer