The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is widely used in software development to build loosely coupled systems where changes in one part of the system trigger actions or updates in other parts without the objects being directly aware of each other.
Important Components
- Subject: An interface or class that maintains a list of observers, provides methods to add/remove observers, and notifies them of state changes.
- Concrete Subject: Implements the Subject interface, tracks its state, and notifies observers when the state changes.
- Observer: An interface or abstract class defining an update() method that observers implement to handle notifications.
- Concrete Observer: Implements the Observer interface, defining how it reacts to state changes in the subject.
- Client: Sets up the subject and observers, attaching observers to the subject and triggering state changes.
How It Works
- The Subject maintains a list of Observers and provides methods to attach(), detach(), and notify() observers.
- The Concrete Subject updates its state and calls notify() to inform all observers of the change.
- The Observer interface declares an update() method, which is called by the subject during notification.
- Concrete Observers implement update() to react to the subject’s state change (e.g., by retrieving the new state).
- The Client configures the system by attaching observers to the subject and initiating state changes.
Sample Implementaion
// Observer Interface
interface Observer {
void update(float temperature, float humidity);
}
// Subject Interface
interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
public WeatherStation() {
observers = new ArrayList<>();
}
@Override
public void attach(Observer observer) {
observers.add(observer);
System.out.println("Attached an observer.");
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
System.out.println("Detached an observer.");
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity);
}
}
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
System.out.println("Weather data updated: Temp = " + temperature + "°C, Humidity = " + humidity + "%");
notifyObservers();
}
}
// Concrete Observers
class PhoneDisplay implements Observer {
private String name;
public PhoneDisplay(String name) {
this.name = name;
}
@Override
public void update(float temperature, float humidity) {
System.out.println(name + " display: Temp = " + temperature + "°C, Humidity = " + humidity + "%");
}
}
class TVDisplay implements Observer {
private String name;
public TVDisplay(String name) {
this.name = name;
}
@Override
public void update(float temperature, float humidity) {
System.out.println(name + " display: Temp = " + temperature + "°C, Humidity = " + humidity + "%");
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Create subject
WeatherStation weatherStation = new WeatherStation();
// Create observers
Observer phoneDisplay = new PhoneDisplay("Phone");
Observer tvDisplay = new TVDisplay("TV");
// Attach observers to subject
weatherStation.attach(phoneDisplay);
weatherStation.attach(tvDisplay);
// Update weather data
weatherStation.setMeasurements(25.5f, 60.0f);
// Detach one observer
weatherStation.detach(phoneDisplay);
// Update weather data again
weatherStation.setMeasurements(28.0f, 55.0f);
}
}
/*
Attached an observer.
Attached an observer.
Weather data updated: Temp = 25.5°C, Humidity = 60.0%
Phone display: Temp = 25.5°C, Humidity = 60.0%
TV display: Temp = 25.5°C, Humidity = 60.0%
Detached an observer.
Weather data updated: Temp = 28.0°C, Humidity = 55.0%
TV display: Temp = 28.0°C, Humidity = 55.0%
*/
Code language: PHP (php)
Advantages
- Loose Coupling: Observers and subjects are decoupled; observers can be added/removed without modifying the subject.
- Dynamic Relationships: Observers can be attached or detached at runtime.
- Broadcast Communication: Supports one-to-many notifications efficiently.
- Reusability: The same observer can work with different subjects, and vice versa.
Disadvantages
- Memory Leaks: Failing to detach observers can lead to memory leaks, as the subject holds references to them.
- Performance Overhead: Notifying a large number of observers can be slow, especially if updates are frequent.
- Complexity: Managing observer registration and notifications adds complexity.
- Unexpected Updates: Observers may receive updates they don’t need, requiring filtering logic.
When to Use
- When an object’s state change needs to notify multiple dependent objects.
- When you want to decouple the object that changes (subject) from the objects that react to changes (observers).
- When you need to support dynamic addition or removal of observers.
- When implementing event-driven systems, such as GUI event listeners or publish-subscribe models.
Real-World Example
- GUI Frameworks: In Java Swing, a button (subject) notifies registered listeners (observers) when clicked.
- News Feed Systems: A news agency (subject) notifies subscribers (observers) of new articles.
- Data Binding in UI: UI components (observers) update automatically when a model (subject) changes (e.g., in MVVM frameworks).
- Event Bus Systems: An event bus notifies subscribers of specific events (e.g., in Android’s EventBus library).
The Observer Pattern is fundamental in building responsive and decoupled systems where changes in one part of the application trigger updates elsewhere. By facilitating the notification of multiple objects in response to state changes, it promotes flexibility, scalability, and maintainability in software design. Understanding and applying this pattern can greatly enhance the modularity and extensibility of your applications, making them more adaptable to changing requirements and environments.