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 hot..

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
       
  • 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