DEV Community

Cover image for Strategy Design Pattern in Python
Timi
Timi

Posted on • Updated on

Strategy Design Pattern in Python

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

Enter fullscreen mode Exit fullscreen mode

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())

Enter fullscreen mode Exit fullscreen mode

The result shows two product IDs. One was generated using the Random Strategy and the other was generated with the Derived Strategy.
Image description

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.

Top comments (0)