Design Patterns: Observer Pattern

Staying connected with the Observer pattern

Apr 26, 2023·

4 min read

Play this article

Now that we've tackled the Singleton pattern, let's tackle the Observer pattern. 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 suitable for projects where the changes to the state of one object need to be communicated to other objects in the system, without them having to constantly check for updates.

One example of a project where the Observer pattern is useful is the implementation of a real-time notification system. In this scenario, there could be multiple users or clients who need to be notified when there is new information available in the system, such as new messages or updates to a shared document. By using the Observer pattern, the system can be designed to automatically notify all the relevant clients when there is new information available, without them having to constantly check for updates.

Another example of a project where the Observer pattern is useful is in the development of a GUI (Graphical User Interface) framework. In this case, the GUI elements such as buttons, text boxes and drop-down menus can be designed as observable objects that can notify their listeners (such as event handlers) when they are clicked or interacted with. This helps to decouple the GUI elements from the event handling code and makes it easier to implement complex interactions between different elements of the interface.

Preferably, the Observer pattern should be used in projects where there is a need for a loosely coupled system that can respond to changes in state flexibly and dynamically. It can help to improve the scalability and maintainability of the system, by reducing the coupling between objects and making it easier to modify the behavior of the system over time.

With this in mind, let's take a look at the Observer pattern being utilized in a sample application.

// Weather Service (Subject)
class WeatherService {
  constructor() {
    this.observers = []; // array to store observers
    this.temperature = 0;
    this.humidity = 0;
    this.windSpeed = 0;
    this.precipitation = 0;
  }

  // Add observer to observers array
  addObserver(observer) {
    this.observers.push(observer);
  }

  // Remove observer from observers array
  removeObserver(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  }

  // Notify all observers of changes in weather conditions
  notifyObservers() {
    this.observers.forEach((observer) => observer.update(this));
  }

  // Set weather conditions and notify observers of changes
  setWeatherConditions(temperature, humidity, windSpeed, precipitation) {
    this.temperature = temperature;
    this.humidity = humidity;
    this.windSpeed = windSpeed;
    this.precipitation = precipitation;
    this.notifyObservers();
  }
}

// UI Components (Observers)
class TemperatureDisplay {
  constructor() {
    this.temperature = 0;
  }

  // Update temperature display with new temperature
  update(weatherService) {
    this.temperature = weatherService.temperature;
    this.render();
  }

  // Render temperature display in UI
  render() {
    console.log(`Temperature: ${this.temperature}°C`);
  }
}

class HumidityDisplay {
  constructor() {
    this.humidity = 0;
  }

  // Update humidity display with new humidity
  update(weatherService) {
    this.humidity = weatherService.humidity;
    this.render();
  }

  // Render humidity display in UI
  render() {
    console.log(`Humidity: ${this.humidity}%`);
  }
}

// Usage
const weatherService = new WeatherService();

const temperatureDisplay = new TemperatureDisplay();
weatherService.addObserver(temperatureDisplay);

const humidityDisplay = new HumidityDisplay();
weatherService.addObserver(humidityDisplay);

weatherService.setWeatherConditions(25, 50, 10, 0.2);
// Output:
// Temperature: 25°C
// Humidity: 50%

In the above code, we define two classes: WeatherService as the subject and TemperatureDisplay and HumidityDisplay as observers. The WeatherService class maintains an array of observers and provides methods to add and remove observers and notify them of changes in weather conditions. The TemperatureDisplay and HumidityDisplay classes implement the update method to receive weather updates from the WeatherService, and the render method to display the weather information in the UI.

In the usage example, we create a new instance of the WeatherService, and two instances of TemperatureDisplay and HumidityDisplay. We then add the two displays as observers to the WeatherService using the addObserver method, and set the initial weather conditions using the setWeatherConditions method. This will trigger the update method of the observers, which will update the temperature and humidity displays with the new weather conditions.

The Observer pattern provides a flexible and decoupled architecture for the weather app, where the WeatherService and UI components can interact without tightly coupling to each other. New UI components can be added dynamically by creating new observer classes that implement the update and render methods, and registering them with the WeatherService using the addObserver method.

Did you find this article valuable?

Support Clint by becoming a sponsor. Any amount is appreciated!