- Published on
Observer Design Pattern
- Authors
- Name
- Benton Li
Prerequisite:
- Some knowledge of programming
- Some knowledge of operating systems
Henlo all, after months of slacking off busy working, I’m back again to pay my delicacy to this little blog website. Today we are going to talk about the observer pattern. I had the chance to explain the Flux architecture to someone new to react.js. Understanding the observer design pattern is quite essential to comprehend Redux and Flux. This serves as a prequel to Flux. So here we go.
Intro
Suppose once in a while, the canteen offers pineapple pizza. How do we inform the analysts about this? Consider two extreme cases:
- Every day, analysts pull visits to the canteen just for this gourmet. Most of the time they don’t see it and return to the pit, dismayed. Time is wasted. “Should’ve ordered McDelivery!”
- Set an alarm that broadcasts to the entire trading floor, “Yo, pineapple pizza is in!”. Meanwhile, Italian analysts be like, ayo wtf?
The normal practice between the two extremes is maintaining a menu that sends notifications to people who bookmarked pineapple pizza. This would avoid wasting time checking in on the canteen or hearing unwanted announcements.
What’s special about this kind of event?
- It can arrive at any time
- One party supplies
- Many parties, but not all, demand
Definition
A simple definition for observer pattern: one subject
curates a collection of observers
that behave in a certain way when the subject’s state changes.
For example, here, the canteen manager (subject) maintains a list of interested employees (observers). Whenever the pineapple pizza is in, analysts will receive a push notification and their reactions could be:
- Run to the canteen floor, having their ApplePay ready before entering the elevator
- (Italian analysts) Run to the canteen floor with their putter, shouting “You’ve mama’ed your last mia”
Let’s look into the roles
Subject (aka publishers)
- Has a collection of observers
- Can add/remove observers
- Can notify observers in a uniform manner
Observer (aka subscribers and listeners)
- Passively wait for notification
- Behave certainly upon notification
Now let’s express the abstraction in C++
// analyst.h
// Analyst (observer/subscriber) interface
#ifndef I_ANALYST_H
#define I_ANALYST_H
#include <string>
class IAnalyst {
public:
virtual void Update(const std::string& message) = 0;
IAnalyst(const std::string& name) : name_(name) {}
virtual std::string name() { return name_; }
private:
std::string name_;
};
#endif
// notifier.h
// FoodNotifier (subject/publisher) interface
#ifndef I_NOTIFIER_H
#define I_NOTIFIER_H
#include <stdlib.h>
#include "analyst.h"
class IFoodNotifier {
public:
virtual void AddSubscriber(IAnalyst *anal) = 0;
virtual void RemoveSubscriber(IAnalyst *anal) = 0;
virtual void Notify(const std::string &message) = 0;
};
#endif
Cool, now let’s implement the subject. For simplicity, we use set
as the collection since we can add or remove a specific subscriber without searching them.
#include <iostream>
#include <list>
/**
* The subject (food notifier) has state (whether pineapple pizza is in) and
* If state changes (pizza is in), subject notifies observers (analysts who subscribed)
* Here we implement the collection as a "list"
*/
class PineapplePizzaNotifier : public IFoodNotifier {
public:
void AddAnalyst(IAnalyst *analyst) override {
list_of_analysts_.push_back(analyst);
}
void RemoveAnalyst(IObserver *analyst) override {
list_of_analysts_.remove(analyst);
}
void Notify() override {
for (IAnalyst *anal: list_of_analysts_) {
anal->Update("pineapple pizza");
}
}
void MakePineapplePizza() {
this->pineapple_pizza_in_ = true;
Notify();
}
private:
list<IAnalyst *> list_of_analysts_; //list of analysts who like pineapple pizza
bool pineapple_pizza_in_ = false;
};
And also implement the behaviors of observers. Normal subscribers will just crave for pineapple pizza. But Italians will yeet the canteen.
#include "analyst.h"
#include <signal.h>
#include <iostream>
#include "notifier.h"
class Analyst : public IAnalyst {
public:
Analyst(const std::string &name) : IAnalyst(name) {}
virtual void Hi() {
std::cout << name() << "\t: Hi I am " << name() << ".\n";
}
void Subscribe(IFoodNotifier *foodNotifier) {
(*foodNotifier).AddSubscriber(this);
}
void Unsubscribe(IFoodNotifier *foodNotifier) {
(*foodNotifier).RemoveSubscriber(this);
}
void Update(const std::string &message) {
if (message == "pineapple pizza in")
std::cout << name() << "\t: Hell yeah" << ".\n";
}
};
/**
* Italian analysts are not big fans of pineapple
*/
class ItalianAnalyst : public Analyst {
public:
ItalianAnalyst(const std::string &name) : Analyst(name) {}
virtual void Hi() {
std::cout << name() << "\t: Ciao, io sono " << name() << ".\n";
}
void Update(const std::string &message) {
if (message == "pineapple pizza in") {
std::cout << name()
<< "\t: Morgan Stanley, you have mama'ed your last mia! \n";
// proceeds to yeet the canteen
raise(SIGSEGV);
}
}
};
Now let’s play with this
#include "analyst.cc"
#include "analyst.h"
#include "notifier.cc"
#include "notifier.h"
int main() {
PineapplePizzaNotifier pineapple_pizza_notifier;
Analyst chris("Chris");
ItalianAnalyst lorenzo("Lorenzo");
Analyst guy("Guy");
chris.Hi();
lorenzo.Hi();
guy.Hi();
// Pizza will be in and out, analysts won't react as they have not subscribe
// the pineapple pizza
pineapple_pizza_notifier.MakePineapplePizza();
// Now analyst subscribed to pineapple pizza
pineapple_pizza_notifier.AddSubscriber(&chris);
pineapple_pizza_notifier.AddSubscriber(&lorenzo);
pineapple_pizza_notifier.AddSubscriber(&guy);
// Lorenzo will yeet the canteen and pizza won't be taken
pineapple_pizza_notifier.MakePineapplePizza();
return 0;
}
Before the subscription, everyone is indifferent to the pineapple pizza. Pizza in and out, just like a normal day.
After the subscription, we will see Lorenzo destroy the canteen. Some analysts won’t get a chance to chomp pineapple pizza and canteen won’t do any further announcement.
Observer pattern in web dev
The above example is not a perfect example. Analysts don’t have to strictly behave as designed. Upon seeing pineapple pizza, maybe they can get something else, like British food! Besides, they can also view the menu on their phone actively instead of receiving notification passively, right?
There is no perfect system design. Nevertheless this example illustrates a nice mechanism for a situation where
- Exists one subject and many, but not all, observers
- The subject’s states, or internal properties, may change arbitrarily
- Observers, behave deterministically to the state change
This is widely used in web design where websites can be a subject that has dozens of states behind the scene. These states reflect properties and values like:
- Is the mini tab opened by the user? (if yes, tab should display)
- What’s the value that the user typed in the input box (if too long, wrap the text, or elongate the shape of the input box)
The components in the website like buttons, input boxes, texts contents can all be observers.
In this website if you press control + k
or command + k
- The website (subject) enters a state so that user could search
- The search bar (observer) pops up
As you move cursor from one search result to another search result
- The cursor position (state) changes.
- The result that the cursor is currently on (observer) gets highlighted
- The result that the cursor was previously on (observer) gets unhighlighted
As you type in something
- The content in the input box (state) changes.
- Several API wrappers (observers) call their APIs
As the query results come in:
- Search bar enters a state where search results are ready.
- A list of search result (observers) update their content
How do I implement this? I could possibly write chains of addListener()
s and Thenables
. Also, a popular choice would be React hooks (e.g. useState, useEffect), which leverages observer pattern.
React, Redux, Flux. What about them? That’s the next blog.