Observer & Strategy Patterns
We now move into Behavioral Patterns, which deal with communication between objects and how responsibilities are assigned. These patterns help manage complex control flows and make your system more flexible to change.
1. The Observer Pattern
The Problem
Imagine a Weather Station that tracks temperature. Multiple displays (mobile app, web dashboard, physical LED screen) need to update whenever the temperature changes. If the Weather Station keeps track of every display and calls them directly, it becomes tightly coupled to those displays. Adding a new type of display would require modifying the Weather Station code.
The Solution
The Observer pattern defines a one-to-many dependency between objects so that when one object (the Subject) changes state, all its dependents (Observers) are notified and updated automatically.
This decouples the Subject from its Observers. The Subject only knows that its observers implement a specific Observer interface.
Java Implementation
import java.util.ArrayList;
import java.util.List;
// 1. Observer Interface
interface Observer {
void update(float temp);
}
// 2. Subject (Observable)
class WeatherStation {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void setTemperature(float temp) {
this.temperature = temp;
notifyObservers();
}
private void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature);
}
}
}
// 3. Concrete Observers
class PhoneDisplay implements Observer {
public void update(float temp) {
System.out.println("Phone Display: Temp updated to " + temp);
}
}
class WindowDisplay implements Observer {
public void update(float temp) {
System.out.println("Window Display: Temp updated to " + temp);
}
}
Pros and Cons
- Pros: Established “loose coupling” between objects. Supports the Open/Closed Principle (you can add new observers without changing the subject).
- Cons: Observers are notified in random order. If not careful, you can create memory leaks if observers aren’t properly removed.
2. The Strategy Pattern
The Problem
Suppose you have a Navigator app. It started with a Walk route algorithm. Later, you added Road and PublicTransport. If you use a single class with many if-else or switch statements to handle these different routing logics, the class becomes massive and hard to maintain.
The Solution
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Instead of the Navigator class implementing all algorithms, it delegates the work to a “strategy” object. The client can swap the strategy at runtime.
Python Example
from abc import ABC, abstractmethod
# 1. Strategy Interface
class RouteStrategy(ABC):
@abstractmethod
def build_route(self, start, end):
pass
# 2. Concrete Strategies
class WalkingStrategy(RouteStrategy):
def build_route(self, start, end):
return f"Walking route from {start} to {end}: 20 mins."
class DrivingStrategy(RouteStrategy):
def build_route(self, start, end):
return f"Driving route from {start} to {end}: 5 mins (heavy traffic)."
# 3. Context
class Navigator:
def __init__(self, strategy: RouteStrategy):
self._strategy = strategy
def set_strategy(self, strategy: RouteStrategy):
self._strategy = strategy
def execute_route(self, start, end):
return self._strategy.build_route(start, end)
# Usage
nav = Navigator(WalkingStrategy())
print(nav.execute_route("Home", "Gym"))
# Change strategy at runtime
nav.set_strategy(DrivingStrategy())
print(nav.execute_route("Home", "Gym"))
Strategy vs. State
Wait, isn’t Strategy similar to the State pattern? Yes, the structure is similar, but the intent is different:
- Strategy is about algorithm selection (the client usually chooses the strategy).
- State is about lifecycle management (the object changes its own state automatically).
Pros and Cons
- Pros: Swapping algorithms at runtime. Isolate implementation details of an algorithm. Avoid massive conditional statements.
- Cons: The client must be aware of the different strategies to choose the right one. Increases the number of classes in the system.