Strategy Design Pattern in Python
Timilehin Olusegun
Posted on February 5, 2023
When developing software applications, depending on the type of application, there are questions to be answered in the design phase of development before writing actual code. One of these questions is, “How do I structure my application?”. Software Design Patterns can help to answer this question of structure. Design patterns help us structure our application in ways that make it robust, maintainable, and easy to understand.
Design patterns are high-level concepts to fix issues relating to structure in a codebase. They are not language specific, so you can use them in any language that supports Object Oriented Programming. In this article, we’ll learn about a popular design pattern called the Strategy Pattern.
Prerequisites
Before we go into strategy pattern, you need to have a basic understanding of Object Oriented Programming. Classes and objects are a core part of the design pattern concept.
What Is The Strategy Pattern?
Strategy Pattern is a design pattern that defines a collection of algorithms and allows these algorithms to be interchangeable at runtime. According to the book on design patterns, “Head First: Design Patterns” by Eric Freeman & Elisabeth Robson,
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
There are scenarios where you can have more than one way of achieving a particular task. These different ways are the algorithms. Traditionally, you tend to think of conditional statements (if-else) as a way of determining which algorithm to use. This method is not a better way to write good and clean code. Also, as the number of algorithms increase, the code becomes longer and harder to maintain.
In cases like this, the strategy pattern is a good solution. The strategy pattern allows you to define classes, called strategies, that contain the implementations of your various algorithms. The main class has a reference to the strategy and you can then pass your preferred strategy to the context. Let's look at an example implementation of the strategy pattern.
Implementation
Let’s say we have an e-commerce application where products are registered. When registering these products, we want to generate unique IDs for each of them. We want to be able to generate IDs using different criteria and parameters. We will use the strategy design pattern to develop two switchable strategies for generating the product ID. First, we’ll have a RandomStrategy
that generates the product ID as a random string. In contrast, another DerivedStrategy
generates the product ID using particulars of the product like the date-time, category, and product SKU.
We have our strategy.py
file. This file contains the family of algorithms (in our case, strategies
) for generating the product ID and their different implementations.
from abc import ABC, abstractmethod
from datetime import datetime
import string
import secrets
class ProductIdStrategy(ABC):
"""An interface for strategy type"""
@abstractmethod
def generate_product_id() -> None:
"""Each class will have its own implementation of this function"""
pass
class RandomStrategy(ProductIdStrategy):
def generate_product_id(self) -> str:
limit = 12
product_id = "".join(secrets.choice(
string.ascii_uppercase+string.digits) for i in range(limit))
return product_id
class DerivedStrategy(ProductIdStrategy):
''' The Derived Strategy will derive the product id from the id, category and SKU of the product'''
def __init__(self, product) -> None:
super().__init__()
self.product = product
def generate_product_id(self) -> str:
id = self.product["id"]
# Get the first 3 characters of the category
category = self.product["category"][:3].upper()
sku = self.product["sku"]
# Get the date string and replace remove the hyphens
date = datetime.strptime(self.product["date_added"], "%Y-%m-%d").date().__str__().replace("-", "")
product_id = "".join([category, "-", date, "-", sku, id,])
return product_id
Then we have a product.py
file. In this file, we implement the different strategies interchangeably.
from strategy import *
class Product:
_strategy: ProductIdStrategy
def __init__(self, strategy: ProductIdStrategy) -> None:
self._strategy = strategy
def get_product_id(self) -> str:
return self._strategy.generate_product_id()
if __name__ == "__main__":
stock = {
"id": "1",
"name": "Maxi Lotion",
"category": "Skin Care",
"sku": "6134752",
"date_added": "2022-12-28",
}
''' Generating the product id using the random strategy '''
strategy = RandomStrategy()
product = Product(strategy)
print("The product id using the Random Strategy is : " + product.get_product_id())
'''
Generating the product id using the derived strategy
The product is passed into to the strategy so as to extract information from it
'''
strategy = DerivedStrategy(stock)
product = Product(strategy)
print("The product id using the Derived Strategy is : " + product.get_product_id())
The result shows two product IDs. One was generated using the Random Strategy
and the other was generated with the Derived Strategy
.
Conclusion
In this article, we have learned what the strategy pattern is and how to implement it. With the strategy design pattern, we can switch between algorithms at runtime without changing the code. The Strategy pattern may be used to select the application's behaviour instead of using conditional expressions.
I hope this article has shown you how to implement the strategy design pattern. You can get the example code discussed in this GitHub repository.
Posted on February 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.