[Design Patterns] 1. The Strategist

5/1/2024

Written by Rui Jie

4 minutes read


A futuristic scenario with a man in front of his computer.

Design patterns are general repeatable solutions to some common problems in software engineering. We’ll start off our Design Patterns series from Strategy, which is a very useful tool to add flexibility by selecting algorithm during runtime, encapsulating algorithms in different strategy classes to promote reusability and maintenability.

Understanding the Strategy Design Pattern

At its core, the Strategy Design Pattern defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable. This pattern allows the algorithm to vary independently of the context using it, providing a flexible way to select algorithms at runtime.

Key Components of the Strategy Design Pattern

The Strategy Design Pattern consists of three key components:

  1. Context: This is the class that contains a reference to a Strategy object and delegates the algorithm-specific behavior to this object.
  2. Strategy: This is the interface or abstract class that defines the algorithm to be used. Concrete strategy classes implement this interface.
  3. Concrete Strategies: These are the implementations of the Strategy interface, each representing a specific algorithm. Implementing the Strategy Design Pattern in Practice

To illustrate the Strategy Design Pattern, let’s consider a simple example of a text editor application that allows users to format text using different strategies, such as plain text, markdown, and HTML.

(Python)

class TextFormatter:
    def format(self, text):
        raise NotImplementedError

class PlainTextFormatter(TextFormatter):
    def format(self, text):
        return text

class MarkdownFormatter(TextFormatter):
    def format(self, text):
        return f"**{text}**"

class HTMLFormatter(TextFormatter):
    def format(self, text):
        return f"<b>{text}</b>"

In this example, TextFormatter is the strategy interface, and PlainTextFormatter, MarkdownFormatter, and HTMLFormatter are concrete strategy classes implementing different formatting strategies. We’ll see how we can utilize these strategies below in TextEditor context.

class TextEditor:
    def __init__(self, formatter):
        self.formatter = formatter

    def format_text(self, text):
        return self.formatter.format(text)

# Instantiate the text editor with different formatting strategies
editor = TextEditor(PlainTextFormatter())
formatted_text = editor.format_text("Hello, world!")
print("Plain Text:", formatted_text)
# Hello, world!

editor = TextEditor(MarkdownFormatter())
formatted_text = editor.format_text("Hello, world!")
print("Markdown:", formatted_text)
# **Hello, world**!

editor = TextEditor(HTMLFormatter())
formatted_text = editor.format_text("Hello, world!")
print("HTML:", formatted_text)
# <br>Hello, world</br>!

In this example, the TextEditor class takes a formatter object in its constructor, which represents the selected formatting strategy. The format_text method of the TextEditor class then uses this strategy to format the given text.

By changing the formatter object passed to the TextEditor constructor, you can switch between different formatting strategies at runtime. This demonstrates the flexibility of the Strategy Design Pattern, allowing you to change the behavior of the TextEditor class without modifying its code.

Real-World Examples of the Strategy Design Pattern

The Strategy Design Pattern is widely used in software development, especially in frameworks and libraries. For example, in the Java programming language, the Comparator interface is a classic example of the Strategy Design Pattern, allowing different sorting strategies to be used with sorting methods.

Best Practices and Tips for Using the Strategy Design Pattern

When using the Strategy Design Pattern, it’s important to follow some best practices:

Use meaningful strategy names: Choose descriptive names for your strategies to make your code more readable and maintainable. Keep strategies independent: Ensure that each strategy is independent and does not rely on the state of the context class. Consider performance implications: Be aware of the performance implications of using the Strategy Design Pattern, especially in scenarios where strategies are created frequently.

Conclusion

The Strategy Design Pattern is a powerful tool for designing flexible and maintainable software. By encapsulating algorithms in separate classes and making them interchangeable, this pattern allows for easy modification and extension of the software’s behavior. When used correctly, the Strategy Design Pattern can greatly improve the design and maintainability of your software.