1. Overview
The Observer pattern allows us to establish a communication channel between two parties: the subject and observer. It's also known as the publish-subscribe pattern. Observers register themselves to the subject for some state change. Additionally, this relation is dynamic in that observers can come and go at runtime. In the end, whenever the state changes, the subject notifies all its observers and updates them with new data.
2. Motives
Let's talk more about the motives and use cases.
Firstly, the observer pattern demands two different roles. The class in the subject role owns or manages some state whereas the observer classes act on the state changes. To have our classes fulfill a single well-defined responsibility, we disallow the observers to access the state directly. In other words, we separate the generation of data from its consumption. Then by applying the pattern, we establish a well-defined channel between the subject and its observers.
Another motive to apply the pattern is that the number of observers can change during the lifetime of the application. New observers can register themselves while the existing ones leave. This motive also plays an important role in shaping the design's contract - i.e. addObserver, removeObserver, etc.
The Observer pattern also allows us to work with multiple observer classes that work on the same data in different ways. The subject doesn't need to know how the observer consumes the data. The contract between two parties enables them to work in a loosely-coupled manner.
3. How To Implement
Now, we'll see how we can implement the Observer pattern. For this purpose, we'll use the weather station example. The weather station publishes the latest weather data. Then the displays that register themselves as observers show the changes for a specific data type.
3.1. Implement Observer
The Observer interface defines the recipient part that acts on the data changes:
public interface Observer {
void update(float temp, float humidity, float pressure);
}
Here, the update method defines three different data types: temp, humidity, and pressure.
Next, we'll provide some Observer implementations:
public class ForecastDisplay implements Observer {
public void update(float temp, float humidity, float pressure) {
// Process the change
System.out.println(String.format("%f\t%f\t%f", temp, humidity, pressure));
}
}
public class StatisticsDisplay implements Observer {
public void update(float temp, float humidity, float pressure) {
// Process the change
System.out.println(String.format("%f\t%f\t%f", temp, humidity, pressure));
}
}
These classes use the same weather data to perform different calculations. For example, ForecastDisplay tries to predict future values, whereas StatisticsDisplay generates charts using historical data.
3.2. Implement Subject
The subject has two main responsibilities. One is managing the observers and providing means to register and unregister them. Secondly, it must notify the observers about the latest state changes.
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
Here, we have the Subject interface. The registerObserver and removeObserver methods carry out observer management duty. And the notifyObservers method handles the notification.
We'll now provide a Subject implementation, WeatherStation:
public class WeatherStation implements Subject {
private final List<Observer> observers;
private float temp;
private float humidity;
private float pressure;
public WeatherStation() {
observers = new ArrayList<>();
}
public void registerObserver(Observer observer) {
if (!observers.contains(observer)) {
this.observers.add(observer);
}
}
public void removeObserver(Observer observer) {
this.observers.remove(observer);
}
public void notifyObservers() {
for (Observer o : observers) {
o.update(temp, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
Whenever the weather values change - by an invocation of setMeasurements -WeatherStation notifies the registered observers. In summary, it iterates over the list of observers and calls their update method with the updated data.
3.3. Sample Application
Lastly, we'll write a sample application to showcase the subject and observers:
public static void main(String[] args) {
final WeatherStation weatherStation = new WeatherStation();
final CurrentConditions currentConditions = new CurrentConditions();
weatherStation.registerObserver(currentConditions);
final ForecastDisplay forecastDisplay = new ForecastDisplay();
weatherStation.registerObserver(forecastDisplay);
final StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
weatherStation.registerObserver(statisticsDisplay);
final Random random = new Random();
for (int i = 0; i < 5; i++) {
weatherStation.setMeasurements(random.nextFloat(), random.nextFloat(), random.nextFloat());
System.out.println("***************************************");
}
}
In this example, we're registering three different observers to the WeatherStation instance.
A sample run prints:
0.567413 0.376702 0.871033
0.567413 0.376702 0.871033
0.567413 0.376702 0.871033
***************************************
0.184101 0.862047 0.626799
0.184101 0.862047 0.626799
0.184101 0.862047 0.626799
...
As seen in the output, each observer prints the results after performing its own calculation on three weather data points.
4. Summary
In this tutorial, we detailed the usage of the Observer pattern in Java. We firstly examined the motives regarding why we use the pattern. Then we provided a sample implementation.
As always, the source code for all examples is available on Github.