Skip to main content

Real World Implementation Of Observer Pattern

Let's say we have a weather station that measures temperature, humidity, and pressure. We have multiple display elements (e.g., Current Conditions Display, Statistics Display, Forecast Display) that show these measurements. When the weather station gets new measurements, all the displays should update.

Real World Implementation

We can model this scenario using the Observer pattern where the weather station is the subject and the displays are observers. Here's how you could implement this in TypeScript:

interface Observer {
update(temperature: number, humidity: number, pressure: number): void;
}

interface Subject {
registerObserver(o: Observer): void;

removeObserver(o: Observer): void;

notifyObservers(): void;
}

class WeatherData implements Subject {
private observers: Observer[];
private temperature: number | undefined;
private humidity: number | undefined;
private pressure: number | undefined;

constructor() {
this.observers = [];
}

registerObserver(o: Observer): void {
this.observers.push(o);
}

removeObserver(o: Observer): void {
const index = this.observers.indexOf(o);
if (index >= 0) {
this.observers.splice(index, 1);
}
}

notifyObservers(): void {
if (
this.temperature !== undefined &&
this.humidity !== undefined &&
this.pressure !== undefined
) {
for (let observer of this.observers) {
observer.update(this.temperature, this.humidity, this.pressure);
}
}
}

measurementsChanged(): void {
this.notifyObservers();
}

setMeasurements(
temperature: number,
humidity: number,
pressure: number
): void {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.measurementsChanged();
}

// other WeatherData methods here
}

class CurrentConditionsDisplay implements Observer {
private temperature: number | undefined;
private humidity: number | undefined;
private pressure: number | undefined;

constructor(private weatherData: Subject) {
this.weatherData.registerObserver(this);
}

update(temperature: number, humidity: number, pressure: number): void {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.display();
}

display(): void {
if (this.temperature !== undefined && this.humidity !== undefined) {
console.log(
`Current conditions: ${this.temperature}F degrees and ${this.humidity}% humidity`
);
} else {
console.log("Weather data is not available");
}
}
}

// client code
const weatherData = new WeatherData();
const currentDisplay = new CurrentConditionsDisplay(weatherData);

// Simulate new weather measurements
weatherData.setMeasurements(80, 65, 30.4);
weatherData.setMeasurements(82, 70, 29.2);

In this example, WeatherData is the Subject, and CurrentConditionsDisplay is an Observer. Whenever WeatherData updates its measurements, it notifies all its observers, which then update their displays. This allows for decoupling between the weather data and the displays, and you could easily add more types of displays as observers in the future.

Advantages Of Observer Pattern

The Observer Pattern offers several advantages, especially in scenarios where you have many objects dependent on the state of one object and you want to keep this state synchronized across all dependents.

Below are some of the advantages this pattern offers, each followed by a code snippet from your implementation as an illustration:

Decoupling of Subject and Observer

In this pattern, subjects and observers are loosely coupled. This means that the subject doesn't need to know anything about its observers, only that they implement the Observer interface.

interface Observer {
update(temperature: number, humidity: number, pressure: number): void;
}

class WeatherData implements Subject {
// The WeatherData only knows about observers in terms of the Observer interface
private observers: Observer[];
//...
}

Dynamic relationships

The Observer Pattern allows you to add and remove observers at runtime, allowing for dynamic relationships. This is more flexible than static relationships defined at compile-time.

class WeatherData implements Subject {
registerObserver(o: Observer): void {
this.observers.push(o);
}

removeObserver(o: Observer): void {
const index = this.observers.indexOf(o);
if (index >= 0) {
this.observers.splice(index, 1);
}
}

//...
}

Broadcast communication

The Observer Pattern supports broadcast communication. The subject can send updates to all observers when its state changes.

class WeatherData implements Subject {
notifyObservers(): void {
if (
this.temperature !== undefined &&
this.humidity !== undefined &&
this.pressure !== undefined
) {
for (let observer of this.observers) {
observer.update(this.temperature, this.humidity, this.pressure);
}
}
}

//...
}

Support for open-ended system

With the Observer Pattern, you can add new observers without modifying the subject's code. This makes the system open for extension.

// Adding another Observer without modifying the Subject
class AnotherDisplay implements Observer {
// Implementation details...
}

const anotherDisplay = new AnotherDisplay(weatherData);

In general, the Observer Pattern can greatly simplify complex systems where multiple objects depend on the state of one object. It provides a clean and robust solution for state synchronization and communication between objects.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle