Overview of the Strategy Pattern The Strategy design pattern is a behavioral pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This pattern matters because it lets the algorithm vary independently from the clients that use it. In a typical application, you might find yourself drowning in `if-else` or `switch` statements to handle different logic branches. The Strategy pattern cleans up this mess by injecting behavior into an application without the core logic needing to know the implementation details. It effectively reduces coupling and adheres to the Open/Closed Principle: your code is open for extension but closed for modification. Prerequisites To get the most out of this tutorial, you should have a solid grasp of: * **Python 3.8+**: Familiarity with modern syntax. * **Object-Oriented Programming (OOP)**: Understanding classes, inheritance, and methods. * **Type Hinting**: Basic knowledge of the `typing` module. * **Functional Basics**: Comfort with the idea that functions can be passed around as arguments. Key Libraries & Tools * **ABC (Abstract Base Classes)**: Used to define formal interfaces in the classic OOP approach. * **Typing (Protocol & Callable)**: Modern tools for structural subtyping and defining function signatures. * **Dataclasses**: A concise way to create classes that primarily store data. * **Tabnine**: An AI-powered code completion assistant used during the implementation to speed up development. Code Walkthrough: Evolution of Implementation 1. The Classic OOP Approach The traditional way involves an Abstract Base Class (ABC) and inheritance. You define a blueprint and force subclasses to implement specific methods. ```python from abc import ABC, abstractmethod class TicketOrderingStrategy(ABC): @abstractmethod def create_ordering(self, tickets: list) -> list: pass class FIFOOrderingStrategy(TicketOrderingStrategy): def create_ordering(self, tickets: list) -> list: return tickets.copy() ``` Here, `TicketOrderingStrategy` acts as the interface. The `CustomerSupport` class would receive an instance of a subclass and call `create_ordering`. While robust, it requires significant boilerplate code. 2. Modern Structural Subtyping with Protocols Python 3.8 introduced Protocols, which allow for "static duct typing." You define the expected method signature, but subclasses don't need to explicitly inherit from the Protocol. ```python from typing import Protocol class TicketOrderingStrategy(Protocol): def create_ordering(self, tickets: list) -> list: ... ``` This removes the rigid inheritance chain and reduces dependencies. As long as your class has a `create_ordering` method, it satisfies the requirement. 3. The Functional Callable Approach In Python, functions are first-class citizens. We can simplify further by using the `__call__` dunder method or raw functions. By defining the strategy as a `Callable`, we treat classes and functions as interchangeable entities. ```python from typing import Callable Type alias for our strategy TicketOrderingStrategy = Callable[[list], list] def fifo_strategy(tickets: list) -> list: return tickets.copy() ``` This is the leanest implementation. The `CustomerSupport` class simply calls the strategy as if it were a function: `ordered_list = self.strategy(self.tickets)`. Syntax Notes * **The `__call__` Dunder**: Implementing this method makes an object "callable" like a function. It is perfect for strategies that need to maintain state while behaving like an algorithm. * **Type Aliases**: Using `TicketOrderingStrategy = Callable[...]` makes your type hints readable without the overhead of a full class definition. * **Duck Typing**: Protocols facilitate duck typing by checking if an object "walks and quacks" like the required interface at type-check time. Practical Examples * **Support Ticket Systems**: Sorting tickets by priority, age (FIFO/LIFO), or random assignment for load balancing. * **E-commerce Checkout**: Implementing various discount strategies (seasonal, bulk, or loyalty) that can be swapped based on the user profile. * **Data Export**: A system that can export data to CSV, JSON, or XML depending on the user's selection. Tips & Gotchas * **Parameter Passing**: Functions are great until you need to pass extra configuration (like a random seed). For these cases, use **Closures** or **Classes**. A closure allows you to "bake in" parameters into a returned function. * **Over-Engineering**: Don't use a full ABC if a simple function will do. Start with the simplest functional approach and only move to classes if you need to manage complex internal state. * **Copying Data**: Always return a copy of the list (e.g., `tickets.copy()`) within your strategy to avoid unintended side effects on the original data source.
Closures
Programming Concepts
Oct 2021 • 1 videos
High activity month for Closures. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Oct 2021
- Oct 1, 2021