Observer Pattern
Observer Pattern
The observer pattern is a behavioral design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes by calling one of their methods
Problem Statement
Build a Weather Station backend service that will publish temperature updates to multiple front-end & IoT displays
I need to turn
on when its
We need to display latest tempreture
No Design Pattern
export class TemperatureDisplay {
update(temperature) {
console.log(
"TemperatureDisplay: Understood! My display is now showing ",
temperature
);
}
}
export class Fan {
update(temperature) {
if (temperature > 25) {
console.log("Fan: Its hot here, turning myself on...");
} else {
console.log("Fan: Its nice and cool, turning myself off...");
}
}
}
export class WeatherStation {
constructor(display1, display2, fan) {
this.temperature = 20;
this.display1 = display1;
this.display2 = display2;
this.fan = fan;
}
setTemperature(temp) {
console.log("WeatherStation: new temperature measurement: ", temp);
this.temperature = temp;
// manually update other displays here
this.display1.update(temp);
this.display2.update(temp);
this.fan.update(temp);
}
}
No Design Pattern
import { WeatherStation, TemperatureDisplay, Fan } from "./no-pattern";
const display1 = new TemperatureDisplay();
const display2 = new TemperatureDisplay();
const fan = new Fan();
const weatherStation = new WeatherStation(display1, display2, fan);
weatherStation.setTemperature(30);
Why is it bad?
- No Separation of Concerns
- Mix up core library & business logic
- Mix up core library & business logic
- Tightly Coupled
- Need to encapsulate area of change
-
Weather Station does not need to operate the displays
- Not Scalable
- Do we need to pass 100 arguments to Weather Station if there are 100 displays to be updated?
Observer Pattern
interface Subject {
registerObserver(o: Observer): void;
removeObserver(o: Observer): void;
notifyObservers(): void;
}
interface Observer {
update(temperature: number): void;
}
export class WeatherStation implements Subject {
private observers: Observer[] = [];
private temperature: number;
registerObserver(o: Observer) {
this.observers.push(o);
}
removeObserver(o: Observer) {
let index = this.observers.indexOf(o);
this.observers.splice(index, 1);
}
notifyObservers() {
for (let observer of this.observers) {
observer.update(this.temperature);
}
}
setTemperature(temp: number) {
console.log("WeatherStation: new temperature measurement: ", temp);
this.temperature = temp;
this.notifyObservers();
}
}
export class TemperatureDisplay implements Observer {
update(temperature: number) {
console.log(
"TemperatureDisplay: Understood! My display is now showing ",
temperature
);
}
}
export class Fan implements Observer {
update(temperature: number) {
if (temperature > 25) {
console.log("Fan: Its hot here, turning myself on...");
} else {
console.log("Fan: Its nice and cool, turning myself off...");
}
}
}
Observer Pattern
import { WeatherStation, TemperatureDisplay, Fan } from "./observer";
const display1 = new TemperatureDisplay();
const display2 = new TemperatureDisplay();
const fan = new Fan();
const weatherStation = new WeatherStation();
weatherStation.registerObserver(fan);
weatherStation.registerObserver(display1);
weatherStation.registerObserver(display2);
weatherStation.setTemperature(10);
SOLID PRINCIPLE
-
The Single Responsibility Principle: a class should have only one reason to change
-
The Open-Closed Principle: classes & methods should be open for extensions, but closed for modifications
-
The Liskov Substitution Principle: subtypes must be substitutable for their base types
-
The Interface Segregation Principle: clients should not be forced to depend on methods that they do not use
- The Dependency Inversion Principle: high level modules should not depend on low level modules; both should depend on abstractions.
Thank you!
Reference
Observer Pattern
By Wan Mohd Hafiz
Observer Pattern
- 289