Observer Design Pattern (Implementation in JS)
Understand the Observer design pattern with a real-world example
I am going to explain the widely used and most frequently asked interview question - Observer Pattern aka Pub/Sub Pattern with a real-world example.
Problem
There is a township with several hundred households. Each house would need some basic necessities like electricity, water, gas, etc. There would be a municipal corporation that would be providing these facilities to each house. A house owner(subscriber) who wants to avail these facilities would have to subscribe for each facility that they need with the municipal corporation(publisher). Once subscribed, all the subscribers should receive the facility that they have subscribed for. A house owner can also unsubscribe from a particular facility that they had already subscribed to.
We will now code a solution for the above problem.
Solution
We need to create a central authority (object) that will
- provide the ability to subscribe(function) to various facilities
- have the ability to supply(function) each facility to the respective subscribers
- provide the option to unsubscribe(function) from a facility
Let's note down how our API will look for the solution to this problem,
const mcorp = new MunicipalCorporation()
// house owners subscribing for various facilities and getting a receipt for them
const h1WaterReceipt = mcorp.subscribe('water', () => console.log('house 1, water'))
const h1ElectricityReceipt = mcorp.subscribe('electricity', () => console.log('house 1, electricity'))
const h2WaterReceipt = mcorp.subscribe('water', () => console.log('house 2, water'))
const h3ElectricityReceipt = mcorp.subscribe('electricity', () => console.log('house 3, electricity'))
const h2GasReceipt = mcorp.subscribe('gas', () => console.log('house 2, gas'))
//municipal corporation starting the supplies for a month
mcorp.supply('electricity')
mcorp.supply('water')
mcorp.supply('gas')
//house owners unsubscribing by using(invoking) the receipts
h1ElectricityReceipt()
h2WaterReceipt()
console.log("---- next month's supply begins ----")
//municipal corporation starting the supplies for the next month
mcorp.supply('electricity')
mcorp.supply('water')
mcorp.supply('gas')
Creating MuncipalCorporation Class
Let's create a class of MunicipalCorporation using function constructor, which will have only one property subscribedHouses which will be an object containing the subscriber details
function MunicipalCorporation () {
this.subscribedHouses = {}
}
Implementing subscribe function
Now let's add the subscribe method to the MunicipalCorporation, via its prototype object, that will help a house owner to subscribe for a facility.
MunicipalCorporation.prototype.subscribe = function(facility, houseConnectionDetails) {
if(!this.subscribedHouses[facility]){
this.subscribedHouses[facility] = []
}
this.subscribedHouses[facility].push(houseConnectionDetails)
}
The subscribe function takes two parameters
- facility(a string) that house owner wants to subscribe for
- houseConnectionDetails(a callback), this callback will be executed when the facility is supplied to the subscriber and also the callback reference will serve as the unique id for the houseOwner, which we will later use to provide the house owner a function to unsubscribe
We will store the houseConnectionDetails callback in an array of the respective facility key
First, we check if the particular facility is not a key of the subscribedHouses object, if so then we create the key and assign an empty array to it.
NOTE: Here as the key is stored in facility param and is dynamic so we use square brackets instead of dot notation
Next, we add the callback to the respective facility array. Cool, we are done with our subscribe function, at least for now.
Implementing supply function
The supply function will accept a facility as a parameter and execute all the callbacks present in that facility's array of the subscribedHouses object
MunicipalCorporation.prototype.supply = function(facility) {
if(this.subscribedHouses[facility]){
this.subscribedHouses[facility].forEach(connection => connection())
}
}
We check if the particular facility exists in the subscribedHouses object and if so we iterate over each houseConnectionDetails in the respective facility array and execute the callback.
Implementing unsubscribe function
If you see the API that we wrote at the start, a house owner will receive a receipt(function) when they subscribe to a facility, and then when they want to unsubscribe they have to use(invoke) that receipt.
So as the receipt function is returned from our subscribe function, we will need to implement the unsubscribe function inside the subscribe function itself
MunicipalCorporation.prototype.subscribe = function(facility, houseConnectionDetails) {
if(!this.subscribedHouses[facility]){
this.subscribedHouses[facility] = []
}
this.subscribedHouses[facility].push(houseConnectionDetails)
return () => {
this.subscribedHouses[facility] = this.subscribedHouses[facility].filter(connectionDetails => connectionDetails !== houseConnectionDetails)
}
}
We iterate over the current facility's array and filter out the current callback reference using the filter higher-order method of Array
Now that we have implemented the solution, the following is the output for the code we wrote at the start
"house 1, electricity"
"house 3, electricity"
"house 1, water"
"house 2, water"
"house 2, gas"
// house1 unsubscribed Electricity
// house2 unsubscribed water
"---- next month's supply begins ----"
"house 3, electricity"
"house 1, water"
"house 2, gas"
implementing with class in JS
The above Observer pattern can also be implemented with the syntactic sugar of class
class MunicipalCorporation {
constructor(){
this.subscribedHouses = {}
}
subscribe(facility, houseConnectionDetails){
if(!this.subscribedHouses[facility]){
this.subscribedHouses[facility] = []
}
this.subscribedHouses[facility].push(houseConnectionDetails)
return () => {
this.subscribedHouses[facility] = this.subscribedHouses[facility].filter(connectionDetails => connectionDetails !== houseConnectionDetails)
}
}
supply(facility){
if(this.subscribedHouses[facility]){
this.subscribedHouses[facility].forEach(connection => connection())
}
}
}
So I hope that now you understood the Observer design pattern clearly. This is a widely used pattern in a lot of libraries and frameworks like redux, react, etc
This pattern can be asked in an interview as, implement observer pattern, pub/sub pattern, or event emitter.
If you have liked my explanation of this design pattern then you would definitely love my javascript and react series on my website - WhatTheCode
And since you are reading this article till the end I would like to share that I was asked to implement this design pattern in various interviews including Adobe