Welcome to our deep dive into Behavioral Patterns! These are design patterns that focus on communication between objects, making your code more flexible, reusable, and maintainable. Today, we'll explore the Observer, Strategy, and Command patterns. Let's get started!
The Observer pattern allows multiple objects (called observers) to be notified automatically of any changes (events) in a single object (subject).
Imagine a social media app where users follow each other. Whenever a user posts something, all their followers should be notified. With Observer, you can easily handle this scenario by having the user (subject) notify their followers (observers) when a post is made.
š” Pro Tip: Use an Observable class to manage the list of observers for a subject.
class Observer:
def update(self, subject, message):
pass
class Subject:
def __init__(self):
self.observers = []
def register_observer(self, observer):
self.observers.append(observer)
def notify_observers(self, message):
for observer in self.observers:
observer.update(self, message)
class ConcreteObserver(Observer):
def update(self, subject, message):
print(f"{self.__class__.__name__} got message: {message}")
class ConcreteSubject(Subject):
def notify_observers(self, message):
for observer in self.observers:
observer.update(self, message)
if __name__ == "__main__":
observer1 = ConcreteObserver()
observer2 = ConcreteObserver()
subject = ConcreteSubject()
subject.register_observer(observer1)
subject.register_observer(observer2)
subject.notify_observers("Hello, Observers!")The Strategy pattern allows an algorithm to be selected at runtime and changes the behavior of an object. This can help reduce complexity in your code.
Imagine a shipping company that offers different shipping methods like standard, express, and overnight. Instead of having a single shipping class with many if-else statements, you can create a strategy interface and multiple concrete implementations for each shipping method.
š” Pro Tip: Use the Context class to encapsulate the strategy object and provide a uniform interface for clients.
from abc import ABC, abstractmethod
class ShippingStrategy(ABC):
@abstractmethod
def calculate_cost(self, weight):
pass
class StandardShipping(ShippingStrategy):
def calculate_cost(self, weight):
return weight * 10
class ExpressShipping(ShippingStrategy):
def calculate_cost(self, weight):
return weight * 15
class OvernightShipping(ShippingStrategy):
def calculate_cost(self, weight):
return weight * 20
class Order:
def __init__(self, shipping_strategy):
self.shipping_strategy = shipping_strategy
def calculate_total_cost(self, weight):
return self.shipping_strategy.calculate_cost(weight)
if __name__ == "__main__":
order = Order(StandardShipping())
total_cost = order.calculate_total_cost(5)
print(f"Total cost: {total_cost}")The Command pattern encapsulates a request as an object, allowing the request to be passed as a method argument, delayed execution, and support for undo/redo operations.
Imagine a text editor with an undo feature. The Command pattern allows you to record the actions (commands) as objects, making it easy to undo or redo them.
š” Pro Tip: Use a queue or stack to manage the commands for undo/redo functionality.
class Command:
def execute(self):
pass
class ConcreteCommand(Command):
def execute(self):
print("ConcreteCommand executed")
class Invoker:
def execute_command(self, command):
command.execute()
if __name__ == "__main__":
command = ConcreteCommand()
invoker = Invoker()
invoker.execute_command(command)What is the main purpose of the Observer pattern?
Now that you've learned about the Observer, Strategy, and Command patterns, you're one step closer to becoming a master of software engineering! Keep practicing, and happy coding! šš»š