TUTORIAL SERIES

Design Patterns in Python: State

The Magic of Transitions

Amir Lavasani
8 min readNov 14, 2023

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.

Understanding the State Pattern

Among the myriad design patterns, the State pattern stands out as a valuable tool for managing an object’s behavior when its state changes.

What Is the State Design Pattern?

At its core, the State pattern allows an object to alter its behavior when its internal state changes. This might sound abstract, but it becomes crystal clear when we dive into the practical implementation.

The problem of state management

Imagine an e-commerce application where the behavior of a shopping cart varies depending on whether it’s empty, has items, or is in the process of checkout. Handling the behavior transitions and business logic for each state within a single class becomes unwieldy and leads to code that is difficult to understand and maintain.

The State pattern allows us to encapsulate these states in separate objects and switch between them seamlessly.

When to Use State Pattern

  1. Dynamic State-Dependent Behavior: The State Pattern is ideal when an object’s behavior depends on its state and must change at runtime.
  2. Eliminating Complex Conditional Statements: You should consider using the State Pattern when you have multiple conditional statements that switch an object’s behavior based on its state.
  3. Seamless State Expansion: The State Pattern is beneficial when you want to add new states to your system without altering existing code.

Comparison with If-Else

Let’s contrast the State pattern with a common alternative: if-else statements for state management. In scenarios with few states, if-else statements can be straightforward. However, as the number of states and transitions grows, if-else statements become unwieldy, challenging to maintain, and prone to errors.

Interconnected circles, Each circle represents a state, and lines connecting them symbolize transitions
Dall-E generated image with the following concept: Interconnected circles, Each circle represents a state, and lines connecting them symbolize transitions

Key Components of the State Pattern

The State pattern is built around three main components:

  1. Context: This class maintains a reference to the current state object and delegates the state-specific behavior to it.
  2. State: The State interface defines a set of methods that encapsulate the behavior associated with a particular state.
  3. Concrete States: These are the classes that implement the State interface. Each concrete state represents a specific state of the context and provides its unique implementation of the state-specific behavior.
Image from refactoring.guru

Real-World Application: E-commerce Checkout

Let’s explore the State pattern in a practical context. Imagine an e-commerce checkout process with diverse states, driven by user actions and cart contents. States include adding items, reviewing the cart, entering shipping information, and making a payment. Each state has distinct behavior and validation requirements.

Challenges in Checkout Management

Managing the checkout process can quickly become a complex task, especially in large e-commerce systems. The code can become unwieldy and challenging to maintain. That’s where the State pattern comes to the rescue. It allows us to cleanly organize the different checkout states and their associated behaviors, making the code more manageable and adaptable to changes.

Implementing the State Pattern in Python

Here’s a step-by-step implementation of the State Design Pattern for the E-commerce checkout scenario in Python.

Step 1: Define the State Interface

In this step, we define the CheckoutState interface, which outlines the methods that concrete states must implement. Each method represents an action in the E-commerce checkout process.

# Step 1: Define the State Interface
class CheckoutState:
def add_item(self, item):
pass

def review_cart(self):
pass

def enter_shipping_info(self, info):
pass

def process_payment(self):
pass

Step 2: Create Concrete State Classes

Concrete state classes are implemented in this step. We have four concrete states representing different stages of the checkout process: EmptyCartState, ItemAddedState, CartReviewedState, and ShippingInfoEnteredState. Each concrete state encapsulates specific behaviors associated with its respective state.

# Step 2: Create Concrete State Classes
class EmptyCartState(CheckoutState):
def add_item(self, item):
print("Item added to the cart.")
return ItemAddedState()

def review_cart(self):
print("Cannot review an empty cart.")

def enter_shipping_info(self, info):
print("Cannot enter shipping info with an empty cart.")

def process_payment(self):
print("Cannot process payment with an empty cart.")


class ItemAddedState(CheckoutState):
def add_item(self, item):
print("Item added to the cart.")

def review_cart(self):
print("Reviewing cart contents.")
return CartReviewedState()

def enter_shipping_info(self, info):
print("Cannot enter shipping info without reviewing the cart.")

def process_payment(self):
print("Cannot process payment without entering shipping info.")


class CartReviewedState(CheckoutState):
def add_item(self, item):
print("Cannot add items after reviewing the cart.")

def review_cart(self):
print("Cart already reviewed.")

def enter_shipping_info(self, info):
print("Entering shipping information.")
return ShippingInfoEnteredState(info)

def process_payment(self):
print("Cannot process payment without entering shipping info.")


class ShippingInfoEnteredState(CheckoutState):
def add_item(self, item):
print("Cannot add items after entering shipping info.")

def review_cart(self):
print("Cannot review cart after entering shipping info.")

def enter_shipping_info(self, info):
print("Shipping information already entered.")

def process_payment(self):
print("Processing payment with the entered shipping info.")

Step 3: Create the Context Class

The context class, CheckoutContext, serves as the manager of the checkout process. It maintains a reference to the current state and delegates actions to that state. The context class ensures a seamless transition between states and keeps track of the current state.

# Step 3: Create the Context Class
class CheckoutContext:
def __init__(self):
self.current_state = EmptyCartState()

def add_item(self, item):
self.current_state = self.current_state.add_item(item)

def review_cart(self):
self.current_state = self.current_state.review_cart()

def enter_shipping_info(self, info):
self.current_state = self.current_state.enter_shipping_info(info)

def process_payment(self):
self.current_state.process_payment()

Step 4: Example of Usage

In this final step, we demonstrate how the E-commerce checkout process flows using the State pattern.

# Step 4: Example of Usage
if __name__ == "__main__":
cart = CheckoutContext()

cart.add_item("Product 1")
cart.review_cart()
cart.enter_shipping_info("123 Main St, City")
cart.process_payment()

GitHub Repo 🎉

Explore all code examples and design pattern implementations on GitHub!

10 Real-World Use Cases for State Pattern

The State Pattern finds applications in various domains, beyond E-commerce. Here are ten real-world use cases where the State Pattern shines:

  1. Document Editing: Tracking the state of a document, such as editing, saving, or printing.
  2. Media Players: Managing the playback states, like play, pause, and stop.
  3. Traffic Lights: Controlling the traffic light’s states, from green to red.
  4. Vending Machines: Handling the states of vending machine transactions.
  5. Game Characters: Managing the behavior of game characters based on their actions.
  6. Workflow Management: Representing the workflow of a process, such as approval workflows in a corporate environment.
  7. Chat Applications: Handling user presence states like online, offline, or away.
  8. Booking Systems: Managing reservations and bookings through various stages.
  9. Mobile Phone States: Handling power states like on, off, or in airplane mode.
  10. Robotics: Controlling the behavior of a robot during different tasks.
Colors flow representing state transitions with the intensity of colors signifying the time spent in each state.
Dall-E generated image with the following concept: Colors flow representing state transitions with the intensity of colors signifying the time spent in each state.

Advantages of State Pattern

The State Pattern offers several advantages:

  1. Simplicity: It simplifies complex conditional statements by encapsulating state-specific behavior.
  2. Maintainability: Adding new states or modifying existing ones is easier without affecting other parts of the code.
  3. Readability: The code becomes more readable and self-explanatory as state transitions are clearly defined.
  4. Testing: Testing individual states becomes straightforward, enhancing the overall code quality.

Considerations and Potential Drawbacks

While the State Pattern is powerful, it’s not always the best choice. Consider the following:

  1. Overhead: In simple cases, using the State Pattern may introduce unnecessary complexity.
  2. Design Complexity: Implementing the pattern incorrectly can lead to excessive class hierarchies.
  3. State Transition Logic: Managing transitions between states can become complex in some scenarios.

State Pattern Variations

  1. Hierarchical States: Sometimes, states are structured hierarchically, like having substates within the “Payment” state, such as “CreditCardPayment” and “PayPalPayment.” The State pattern can accommodate hierarchical state machines, simplifying the management of complex systems with nested states.
  2. Concurrent States: In certain situations, objects can exist in multiple states simultaneously. For instance, in an online game, a character may simultaneously be “Moving” and “Attacking.” The State pattern can adapt to handle concurrent states, allowing objects to perform multiple state-specific actions concurrently.

Python State Management Library

  • Transitions: A popular library, offering an expressive and readable approach for defining states and transitions, appealing to developers looking for a declarative method.
An intricate abstract labyrinth with pathways and junctions representing state transitions; The complexity of the labyrinth symbolizes the intricacies of managing states in software.
Dall-E generated image with the following concept: An intricate abstract labyrinth with pathways and junctions representing state transitions; The complexity of the labyrinth symbolizes the intricacies of managing states in software.

Relations with Other Patterns

Let’s briefly compare the State pattern to two other influential design patterns, Bridge and Strategy. While each has its unique focus, there are shared elements to consider.

State vs. Bridge Pattern

Both State and Bridge patterns emphasize decoupling, albeit with different purposes. State manages an object’s behavior based on state changes, while Bridge separates abstractions from implementations. Both patterns promote “composition over inheritance,” addressing distinct design facets.

  • State: Emphasizes state-dependent behavior management.
  • Bridge: Focuses on decoupling abstractions from their implementations.

State vs. Strategy Pattern

State and Strategy patterns encapsulate different behaviors but differ in context. State adapts behavior based on internal states, whereas Strategy dynamically selects algorithms at runtime. The State essentially extends the Strategy concept by encompassing state-dependent behavior.

  • State: Adapts behavior based on internal state.
  • Strategy: Focuses on dynamic algorithm selection at runtime. The State pattern extends this concept to include state-dependent behavior.

Conclusion

In this extensive exploration of the State design pattern, we’ve delved into its core concepts, practical use within e-commerce checkout, and its advantages over conditional statements. We’ve covered complex state transitions, State pattern variations, testing and debugging methods, and Python libraries for state management.

By embracing the State pattern, you can enhance the modularity, maintainability, and extensibility of your Python code — crucial in complex applications like e-commerce systems. We trust this article has equipped you with the knowledge and inspiration to integrate the State pattern into your software projects, ultimately transforming your code’s organization and maintainability.

Hope you enjoyed the State pattern exploration 🙌 Have fun coding 👨‍💻

Next on the Series 🚀

Read More 📜

The Series 🧭

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru State
  3. Head First Design Patterns (Book)
  4. State Pattern in Python
  5. State Method — Python Design Patterns
  6. Evolving the State Design Pattern

--

--

Amir Lavasani

I delve into machine learning 🤖 and software architecture 🏰 to enhance my expertise while sharing insights with Medium readers. 📃