Behavioral Design Patterns
=====================================
Behavioral design patterns are a class of structural and behavioral design patterns that focus on how objects interact with each other, rather than the structure or attributes of those objects. These patterns describe specific ways to solve problems by defining how to create classes that can adapt to different scenarios.
1. Strategy Pattern
The strategy pattern is a behavioral design pattern that allows clients to choose which strategy to use at runtime without changing the object itself.
Description
The strategy pattern defines an interface for strategies and concrete classes that implement these interfaces. A client of this pattern uses the strategy to select the desired behavior.
Example Use Case
Suppose you have a financial application where users can choose between different payment methods (credit card, PayPal, bank transfer). The strategy pattern would be used to define an interface for each method and implement it using concrete classes.
Code Example
from abc import ABC, abstractmethod
# Strategy Interface
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
# Concrete Strategies
class CreditCard(PaymentStrategy):
def __init__(self, card_number, expiration_date, CVV):
self.card_number = card_number
self.expiration_date = expiration_date
self.CVV = CVV
def pay(self, amount):
print(f"Paying ${amount} using credit card {self.card_number}")
class PayPal(PaymentStrategy):
def __init__(self, email):
self.email = email
def pay(self, amount):
print(f"Paying ${amount} using PayPal account {self.email}")
# Client Code
class FinancialApplication:
def __init__(self, payment_strategy):
self.payment_strategy = payment_strategy
def make_payment(self, amount):
self.payment_strategy.pay(amount)
# Usage
credit_card = CreditCard("1234-5678-9012-3456", "12/2025", "123")
paypal = PayPal("example@example.com")
financial_app = FinancialApplication(credit_card)
financial_app.make_payment(100)
2. State Pattern
The state pattern is a behavioral design pattern that defines a class as an object that can change its behavior when interacted with.
Description
The state pattern provides a way to encapsulate an object’s internal state and define how it should change over time. This allows for more flexibility in designing objects that require dynamic behavior.
Example Use Case
Suppose you have a vending machine that can be in different states (open, closed, or maintenance). The state pattern would allow you to simulate these states by defining classes with attributes and methods that represent the different states.
Code Example
from abc import ABC, abstractmethod
# State Interface
class VendingMachineState(ABC):
@abstractmethod
def display_message(self):
pass
# Concrete States
class OpenVendingMachine(VendingMachineState):
def __init__(self):
self.is_open = True
def display_message(self):
print("Vending machine is open")
class ClosedVendingMachine(VendingMachineState):
def __init__(self):
self.is_open = False
def display_message(self):
print("Vending machine is closed")
# Context Class
class VendingMachine:
def __init__(self, state=None):
self.state = state if state else OpenVendingMachine()
def open(self):
self.state = OpenVendingMachine()
print("Opening vending machine")
return self
def close(self):
self.state = ClosedVendingMachine()
print("Closing vending machine")
# Client Code
vending_machine = VendingMachine()
vending_machine.open().close()
3. Observer Pattern
The observer pattern is a behavioral design pattern that defines an object as an event source and allows other objects to be notified when the state of an object changes.
Description
The observer pattern provides a way for clients to discover and respond to changes in an object’s behavior without having direct access to its implementation. This allows for more flexibility in designing applications with dynamic data.
Example Use Case
Suppose you have a weather service that notifies users when the temperature or precipitation levels change. The observer pattern would be used to define classes for the weather service and the subscribers (users) who want to receive notifications.
Code Example
from abc import ABC, abstractmethod
# Subject Interface
class WeatherService(ABC):
@abstractmethod
def notify(self):
pass
# Concrete Subjects
class TemperatureWeatherService(WeatherService):
def __init__(self, temperature):
self.temperature = temperature
def notify(self):
print(f"Temperature is {self.temperature} degrees Celsius")
class PrecipitationWeatherService(WeatherService):
def __init__(self, precipitation):
self.precipitation = precipitation
def notify(self):
print(f"Precipitation is {self.precipitation} mm")
# Subject
class WeatherStation:
def __init__(self):
self.weather_services = []
def add_weather_service(self, weather_service):
self.weather_services.append(weather_service)
def notify_subscribers(self):
for service in self.weather_services:
service.notify()
# Client Code
weather_station = WeatherStation()
temperature_service = TemperatureWeatherService(25)
precipitation_service = PrecipitationWeatherService(10)
weather_station.add_weather_service(temperature_service)
weather_station.add_weather_service(precipitation_service)
weather_station.notify_subscribers()
4. Model-View-Presenter (MVP) Pattern
The MVP pattern is a behavioral design pattern that separates the model, view, and presenter into distinct layers to improve testability and maintainability.
Description
The MVP pattern provides a way for developers to separate concerns by defining classes that represent each layer of an application. The model represents the data, the view represents the user interface, and the presenter handles business logic.
Example Use Case
Suppose you have a banking system with a user interface (GUI) where users can log in or register. The MVP pattern would be used to define separate classes for the model, view, and presenter.
Code Example
from abc import ABC, abstractmethod
# Model Interface
class UserModel(ABC):
@abstractmethod
def get_user_id(self):
pass
# Concrete Models
class RegisteredUser(UserModel):
def __init__(self, user_id):
self.user_id = user_id
def get_user_id(self):
return self.user_id
# View Interface
class GUIView(ABC):
@abstractmethod
def render(self):
pass
# Concrete Views
class LoginGUIView(GUIView):
def render(self):
print("Login GUI")
# Presenter Interface
class UserPresenter(ABC):
@abstractmethod
def login(self, user_id):
pass
# Concrete Presenters
class RegisteredUserPresenter(UserPresenter):
def __init__(self, model, view):
self.model = model
self.view = view
def login(self, user_id):
print(f"Logging in with user ID {user_id}")
# Client Code
model = RegisteredUser(1)
view = LoginGUIView()
presenter = RegisteredUserPresenter(model, view)
presenter.login(1)
Conclusion
Behavioral design patterns provide a way to structure code that is more modular, maintainable, and flexible. By using these patterns, developers can create applications with dynamic behavior that can adapt to changing requirements.
The behavioral design patterns discussed in this article are:
- Strategy Pattern: Define an interface for strategies and concrete classes that implement it.
- State Pattern: Represent different states of an object and provide methods to change the state.
- Observer Pattern: Define an object as an event source and allow other objects to be notified when the state changes.
- Model-View-Presenter (MVP) Pattern: Separate concerns by defining classes that represent each layer of an application.
By applying these patterns, developers can create more robust, scalable, and maintainable software systems.