Behavioral Design Patterns 🧩
Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. They characterize complex control flow and focus on how objects communicate. They improve flexibility and reusability in software design.
Common Behavioral Patterns and Examples 💡
- Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
- Observer: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
- Template Method: Defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
- Iterator: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
- Command: Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
- Chain of Responsibility: Avoids coupling the sender of a request to its receiver by giving multiple objects a chance to handle the request. The chain of receiving objects is passed along until an object handles the request.
- Mediator: Defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
- Memento: Without violating encapsulation, captures and externalizes an object’s internal state so that the object can be restored to this state later.
- State: Allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
- Visitor: Represents an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
Strategy Pattern Example 💻
Here's a simple example of the Strategy pattern in Python:
class Strategy:
def execute(self, data):
pass
class ConcreteStrategyA(Strategy):
def execute(self, data):
return data.lower()
class ConcreteStrategyB(Strategy):
def execute(self, data):
return data.upper()
class Context:
def __init__(self, strategy: Strategy):
self._strategy = strategy
def set_strategy(self, strategy: Strategy):
self._strategy = strategy
def execute_strategy(self, data):
return self._strategy.execute(data)
# Client code
context = Context(ConcreteStrategyA())
print(context.execute_strategy("Hello")) # Output: hello
context.set_strategy(ConcreteStrategyB())
print(context.execute_strategy("Hello")) # Output: HELLO
Observer Pattern Example 📢
Here's a basic example of the Observer pattern in Python:
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
class Observer:
def update(self, message):
pass
class ConcreteObserverA(Observer):
def update(self, message):
print(f"ConcreteObserverA received: {message}")
class ConcreteObserverB(Observer):
def update(self, message):
print(f"ConcreteObserverB received: {message.upper()}")
# Client code
subject = Subject()
observer_a = ConcreteObserverA()
observer_b = ConcreteObserverB()
subject.attach(observer_a)
subject.attach(observer_b)
subject.notify("Hello, observers!")
# Output:
# ConcreteObserverA received: Hello, observers!
# ConcreteObserverB received: HELLO, OBSERVERS!
Benefits of Using Behavioral Patterns ✅
- Increased Flexibility: Allows for dynamic changes in object behavior.
- Loose Coupling: Reduces dependencies between objects, improving modularity.
- Improved Reusability: Facilitates the reuse of algorithms and behaviors across different contexts.
- Enhanced Maintainability: Simplifies code maintenance and modification through clear object interactions.