Photo by Caspar Camille Rubin on Unsplash
Design Patterns: Observer Pattern
Staying connected with the Observer pattern
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.