The observer pattern, also known as the publisk-subscribe pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified.
The observer model in life
Mr. Li recently had his eye on an apartment, only to be told when he arrived at the sales office that the building was sold out. Fortunately, sales han Meimei told Li Lei, soon there are some tail launch, developers are dealing with the relevant procedures, procedures can be bought. But when exactly, no one knows yet.
So Li Lei wrote down the phone of the sales office, every day will call the past to ask whether it has arrived at the time to buy. In addition to Li Lei, Lin Tao, Wei Hua will also consult the sales office every day. A week later, han Meimei, a salesman, decided to quit because she was tired of answering 1,000 phone calls a day with the same content.
Of course, there is no such stupid sales company in reality. In fact, the story is like this: Li Lei left his phone number in the sales office before he left. Sales han Meimei promised him, a new launch immediately send information notice Li Lei. Lin Tao and Wei Hua are the same, their phone numbers are recorded in the sales office roster, the launch of new properties, sales han Meimei will open the roster, traversing the above phone number, in turn to send a text message to inform them.
In the example above, sending SMS notifications is a typical observer model. Buyers such as Li Lei and Lin Tao are subscribers who subscribe to the news of the sale of their house. The sales office, as a publisher, will traverse the phone numbers on the roster at the right time and release information to buyers in turn.
The observer pattern allows two objects to be loosely coupled together, not knowing much about each other, but not preventing them from communicating with each other. When a new subscriber appears, the publisher’s code does not need to be modified; Also, if the publisher needs to change, it will not affect the previous subscribers. As long as the previously agreed event names have not changed, you are free to change them.
The practice of the observer model
In the observer model, at least two key roles must be present — publisher and subscriber. In object-oriented terms, there are two classes.
Let’s start with the class that represents the Publisher, which we’ll call Publisher. What “basic skills” should this class have? Remember han Meimei above, what is the basic operation of Han Meimei? The first is to add subscribers, and the second is to notify subscribers, which are the most obvious. Han meimei also has the ability to remove subscribers. Now that the three basic capabilities of the publisher class are complete, let’s start writing code:
// Define the publisher class
class Publisher {
constructor() {
this.observers = []
}
add(observer) {
console.log('Add subscribers! ')
this.observers.push(observer)
}
remove(observer) {
console.log('Remove the subscriber! ')
this.observers.forEach((item, i) = > {
if (item === observer) {
this.observers.splice(i, 1)}}}notify() {
console.log('Inform all subscribers! ')
this.observers.forEach((observer) = > {
observer.update(this)}}}Copy the code
With the publisher out of the way, let’s think about what the subscriber can do — the subscriber’s power is very simple, as a passive party, it has only two actions — to be notified and to perform. Since we are doing method calls in Publisher, all we need to do in the subscriber class is define the method:
// Define the subscriber class
class Observer {
update() {
console.log("Received notice!")}}Copy the code
Let’s take a look at how Li Lei, Lin Tao and Han Meimei implemented the observer model:
// Create subscriber: buy a house Li Lei
const liLei = new Observer();
// Create subscriber: Lin Tao
const linTao = new Observer();
const hanMeiMei = new Publisher();
// Han Meimei records buyers
hanMeiMei.add(liLei);
hanMeiMei.add(linTao);
// Inform all buyers of the new property
hanMeiMei.notify()
Copy the code
I believe that by this step, you have a good grasp of the core idea of the observer mode, the basic implementation mode.
The global observer object
Recall the observer mode just realized, we have added subscription and publishing functions to the sales office object and login object, Li Lei and Han Meimei still have a certain coupling, Li Lei at least know the name of the sales office object, in order to successfully subscribe to the event. If Li Lei also cares about another sales department, it means that Li Lei needs to start subscribing to another object, which is actually a waste of resources.
In fact, in reality, to buy a house is not necessarily to go to the sales office, we only need to submit the subscription request to the intermediary company, and the major real estate companies only need to release the house information through the intermediary company. In this way, we don’t care which real estate company the news comes from, we care if the news is received smoothly. Of course, in order for the subscriber and the publisher to communicate smoothly, both the subscriber and the publisher must be aware of the intermediary company.
In the same application, we can use a global EventEmitter object. Subscribers don’t need to know which publisher the message is coming from, and publishers don’t know which subscribers the message is being sent to. EventEmitter acts as a kind of “intermediary” linking subscribers and publishers. Let’s implement an Event Bus together:
class EventEmitter {
constructor() {
Handlers are a map that stores the mapping between events and callbacks
this.handlers = {}
}
The // on method is used to install an event listener, which takes a target event name and a callback function as parameters
on(eventName, cb) {
// Check whether the target event name has a listener queue
if (!this.handlers[eventName]) {
// If not, initialize a listener queue first
this.handlers[eventName] = []
}
// Push the callback into the listener queue of the target event
this.handlers[eventName].push(cb)
}
The emit method is used to fire the target event, which takes the event name and listener function input arguments
emit(eventName, ... args) {
// Check whether the target event has a listener queue
if (this.handlers[eventName]) {
Handlers [eventName] is made a shallow copy of this. Handlers [eventName]. The main purpose is to avoid sequencing problems when removing listeners installed through once
const handlers = this.handlers[eventName].slice()
// If so, call each of the queue's callback functions one by one
handlers.forEach((callback) = >{ callback(... args) }) } }// Remove the specified callback function from an event callback queue
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if(index ! = = -1) {
callbacks.splice(index, 1)}}// Register a single listener for the event
once(eventName, cb) {
// Wrap the callback function so that it is automatically removed after execution
const wrapper = (. args) = >{ cb(... args)this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}
Copy the code
The “crew” of Event Bus (Vue, Flutter, etc.) and Event Emitter (Node, etc.) are different, but they all correspond to a common role — global Event Bus.
The global event bus is not strictly observer mode, but publish-subscribe mode. Event Bus/Event Emitter, as a global Event Bus, functions as a communication bridge. We can think of it as an event center, where subscriptions/publishing of all our events cannot be “privately communicated” between subscribers and publishers, and must be delegated to this event center for us.
conclusion
The advantages of the observer mode are obvious: one is the decoupling in time, and the other is the decoupling between objects. It has a wide range of applications, from asynchronous programming to more loosely coupled code writing. The observer pattern can also be used to help implement other design patterns, such as the mediator pattern.
Of course, the observer model is not without its drawbacks. Creating a subscriber itself takes time and memory, and when you subscribe to a message, the message may never happen, but the subscriber will always be in memory. In addition, the observer mode can weaken the connections between objects, but if overused, the necessary connections between objects can be buried in the background, making it difficult for programs to track, maintain, and understand.