Observer Pattern from Head First Design Patterns

In this post, we will learn simple Observer Pattern implementation with examples from Head First Design Patterns Book.
Table of contents
  1. Overview
  2. Class Diagram
  3. Implementation with Example
  4. Conclusion (Source code on Github Repository)
  5. References

1. Overview

The Observer Design Pattern defined
Defines a one-to-many dependency between objects so that when one object changes state,
all its dependents are notified and updated automatically.
Key points about observer pattern
  1. The Observer Pattern defines a one-to-many relationship between objects.
  2. When the state of one object changes, all of its dependents are notified.
  3. Observers are loosely coupled in that the Observable knows nothing about them, other than they implement the Observer Interface.
  4. Loosely coupled designs allow us to build flexible OO Systems that can handle change because they minimize the interdependency between objects.
  5. Java has several implementations of the Observer Pattern, including the general purpose java.util.Observable.
Strive for loosely coupled designs between objects that interact.

2. Class Diagram

3. Implementation with Example


In this example, I have used sample Weather Station Application from Head First Design Pattern Book.

Designing the Weather Station using Observer Pattern

  • WeatherData class is the "one" and "many" is the various elements that use the weather measurements.
  • WeatherData class certainly has stated, that's the temperaturehumidity, and barometric pressure, those definitely changes.
  • When measurements are changing, we have to notify all the display elements so they can do whatever it is they are going to with the measurements.
Let's apply the Observer Pattern to solve the Weather Station Problem.
Step 1: Create Observer interface and DisplayElement Interface for display common method.
public interface Observer {
 public void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
 public void display();
}
Step 2: Create a Subject interface.
public interface Subject {
 public void registerObserver(Observer o);
 public void removeObserver(Observer o);
 public void notifyObservers();
}
Step 3: Implementing the Subject Interface in WeatherData. Let's create WeatherData class to implement the Subject interface.
import java.util.*;

public class WeatherData implements Subject {
 private ArrayList<Observer> observers;
 private float temperature;
 private float humidity;
 private float pressure;
 
 public WeatherData() {
  observers = new ArrayList<>();
 }
 
 public void registerObserver(Observer o) {
  observers.add(o);
 }
 
 public void removeObserver(Observer o) {
  int i = observers.indexOf(o);
  if (i >= 0) {
   observers.remove(i);
  }
 }
 
 public void notifyObservers() {
  for (int i = 0; i < observers.size(); i++) {
   Observer observer = (Observer)observers.get(i);
   observer.update(temperature, humidity, pressure);
  }
 }
 
 public void measurementsChanged() {
  notifyObservers();
 }
 
 public void setMeasurements(float temperature, float humidity, float pressure) {
  this.temperature = temperature;
  this.humidity = humidity;
  this.pressure = pressure;
  measurementsChanged();
 }
 
 public float getTemperature() {
  return temperature;
 }
 
 public float getHumidity() {
  return humidity;
 }
 
 public float getPressure() {
  return pressure;
 }
}
Step 4: Create an Observer ForecastDisplay class.
public class ForecastDisplay implements Observer, DisplayElement {
 private float currentPressure = 29.92f;  
 private float lastPressure;
 private WeatherData weatherData;

 public ForecastDisplay(WeatherData weatherData) {
  this.weatherData = weatherData;
  weatherData.registerObserver(this);
 }

 public void update(float temp, float humidity, float pressure) {
                lastPressure = currentPressure;
  currentPressure = pressure;

  display();
 }

 public void display() {
  System.out.print("Forecast: ");
  if (currentPressure > lastPressure) {
   System.out.println("Improving weather on the way!");
  } else if (currentPressure == lastPressure) {
   System.out.println("More of the same");
  } else if (currentPressure < lastPressure) {
   System.out.println("Watch out for cooler, rainy weather");
  }
 }
}
Step 5: Create a second Observer HeatIndexDisplay class.
public class HeatIndexDisplay implements Observer, DisplayElement {
 float heatIndex = 0.0f;
 private WeatherData weatherData;

 public HeatIndexDisplay(WeatherData weatherData) {
  this.weatherData = weatherData;
  weatherData.registerObserver(this);
 }

 public void update(float t, float rh, float pressure) {
  heatIndex = computeHeatIndex(t, rh);
  display();
 }
 
 private float computeHeatIndex(float t, float rh) {
  float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) 
   + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) 
   + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
   (0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * 
   (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + 
   (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
   0.000000000843296 * (t * t * rh * rh * rh)) -
   (0.0000000000481975 * (t * t * t * rh * rh * rh)));
  return index;
 }

 public void display() {
  System.out.println("Heat index is " + heatIndex);
 }
}
Step 6: Create a third Observer StatisticsDisplay class.
public class StatisticsDisplay implements Observer, DisplayElement {
 private float maxTemp = 0.0f;
 private float minTemp = 200;
 private float tempSum= 0.0f;
 private int numReadings;
 private WeatherData weatherData;

 public StatisticsDisplay(WeatherData weatherData) {
  this.weatherData = weatherData;
  weatherData.registerObserver(this);
 }

 public void update(float temp, float humidity, float pressure) {
  tempSum += temp;
  numReadings++;

  if (temp > maxTemp) {
   maxTemp = temp;
  }
 
  if (temp < minTemp) {
   minTemp = temp;
  }

  display();
 }

 public void display() {
  System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
   + "/" + maxTemp + "/" + minTemp);
 }
}
Step 7: Create a fourth Observer CurrentConditionsDisplay class.
public class CurrentConditionsDisplay implements Observer, DisplayElement {
 private float temperature;
 private float humidity;
 private Subject weatherData;
 
 public CurrentConditionsDisplay(Subject weatherData) {
  this.weatherData = weatherData;
  weatherData.registerObserver(this);
 }
 
 public void update(float temperature, float humidity, float pressure) {
  this.temperature = temperature;
  this.humidity = humidity;
  display();
 }
 
 public void display() {
  System.out.println("Current conditions: " + temperature 
   + "F degrees and " + humidity + "% humidity");
 }
}
Step 8: Create WeatherStation class to test the observer design pattern.
public class WeatherStation {

 public static void main(String[] args) {
  WeatherData weatherData = new WeatherData();
 
  CurrentConditionsDisplay currentDisplay = 
   new CurrentConditionsDisplay(weatherData);
  StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
  ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

  weatherData.setMeasurements(80, 65, 30.4f);
  weatherData.setMeasurements(82, 70, 29.2f);
  weatherData.setMeasurements(78, 90, 29.2f);
 }
}
Output:
Current conditions: 80.0F degrees and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast: Improving weather on the way!
Current conditions: 82.0F degrees and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast: Watch out for cooler, rainy weather
Current conditions: 78.0F degrees and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast: More of the same

4. Conclusion

In this post, we have learned what is Observer Pattern with examples from Head First Design Patterns book. Also, we have solved and designed Weather Station problem using Observer Pattern. There is a separate post of Observer Design Pattern which explains class diagrams, examples and real-world examples.
All the source code for this post available on Github Repo. This is eclipse project so you can import into your workspace and play with it.

5. Reference

Comments