Observer design pattern using javaScript
In this blog post, we will discuss an observer design pattern using javascript from scratch.
What is an Observer Pattern? #
Observer design pattern is a behavioral pattern that allows one object to notify its change to another object without knowing the details of the other object promoting loose coupling. A subject (an object) maintains a list of observers (objects) and notifies them automatically of any changes, usually by calling one of their methods. It defines a one-to-many relationship between objects, such that when the subject changes state, all its dependents (observers) are notified and updated automatically.
Note: Behavioral pattern is concerned about combining objects and defining how they communicate with each other. The goal of this pattern is to create reusable, extensible and maintainable code. It is used to reduce dependencies between objects and communicate between disparate objects in a system.
How to implement an observer pattern using javascript? #
Let's first understand the roles and responsibilities of a Subject.
-
A subject should maintain a list of observers.
=> We can use an
array
to store observers. -
A subject should provide a way to register observers.
=> We can add a
subscribe
method. -
A subject should provide a way to cancel the observer registration.
=> We can add a
unsubscribe
method. -
A subject should notify all observers when a change occurs in its state.
=> We can use
notify
method.
Now, let's model this using a class
in javascript. We could also do this using a function construction and prototype but we will use class for simplicity.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter((obs) => obs !== observer);
}
notify(change) {
this.observers.forEach((observer) => {
observer.update(change);
});
}
}
Now, let's go through the roles and responsibilities of an Observer.
-
An observer should have a method which will be called by the subject when a change occurs.
=> We can use
update
method.
class Observer {
constructor() {}
update(message) {
console.log(message);
}
}
Note: Method names used in both Subject and Observer of the observer pattern are not standard. We can use any name that expresses the intent of the method as per the observer pattern standard. Also, both the subject and observers might have other properties and methods in the class representing state and other behaviours.
The following code shows how to use the observer pattern in action.
// create multiple observers
const observer1 = new Observer();
const observer2 = new Observer();
// create a subject
const subject = new Subject();
// register the observers
subject.subscribe(observer1);
subject.subscribe(observer2);
// notify all observers about the change
// notify method can send anything such as a message,data,etc.
subject.notify("Hello World");
The above code will print the following output for each observer.
Output
Hello World
Hello World
If any any of the observers are not interested in receiving notifications, they can unsubscribe from the subject as follows:
subject.unsubscribe(observer1);
subject.notify("Second Message");
When the next message is broadcasted by the subject, since observer1 has already unsubscribed, only observer2 will receive the message.
Output
Second Message
Note: The order of calling the
notify
method of theSubject
matters and should be called only after registering all the observers.
So far, we have only gone through the concepts. Let's see how to use this pattern with an example.
Imagine you are building a system for a bank to create joint accounts. In this system, we have to notify the users when a change occurs in their account. For example, when a user withdraws money from their account, we have to notify them about the change in their account balance.
Let's create a JointAccount
class by extending the Subject
class which we defined earlier. This class have methods to withdraw
and deposit
money from the account. It also has a method to add
an owner to the account. When an owner is added to the account, they will be automatically registered to receive notifications when a change occurs in their account.
class JointAccount extends Subject {
constructor(balance) {
super();
this.balance = balance;
}
withdraw(amount) {
this.balance -= amount;
// this method is inherited from Subject class
this.notify(`${amount} withdrawn from JointAccount`);
}
deposit(amount) {
this.balance += amount;
// this method is inherited from Subject class
this.notify(`${amount} deposited on JointAccount`);
}
addOwner(owner) {
this.subscribe(owner);
}
}
Note: Since
JointAccount
class extendsSubject
, it will have all the methods of Subject. So, we don't have to define the methods again.
Now, let's create a AccountOwner
class by extending an Observer
class which we defined earlier.
class AccountOwner extends Observer {
constructor(name) {
super();
this.name = name;
}
// this method is inherited from Observer class
// this method is also overridden
update(message) {
console.log(`${this.name} received a notification: ${message}`);
}
}
Finally, it's time to create a joint account and add some owners to it.
// create a joint account
const jointAccount = new JointAccount(10000);
// create two joint owners
const firstAccountOwner = new AccountOwner("Sophie");
const SecondAccountOwner = new AccountOwner("Norah");
// add joint owners to the joint account
// this will automatically register the owners to receive notifications
jointAccount.addOwner(firstAccountOwner);
jointAccount.addOwner(SecondAccountOwner);
To test the notification, we can withdraw
some money from the joint account and see what happens.
jointAccount.withdraw(1000);
When we withdraw
money from the joint account, the notify
method of the Subject
class will be called which will in turn call the update
method of the AccountOwner
class.
Output
Sophie received a notification: 1000 withdrawn from JointAccount
Norah received a notification: 1000 withdrawn from JointAccount
Also, let's deposit
some money in the joint account and see what happens.
jointAccount.deposit(1000);
When we deposit
money in the joint account, it is the same behaviour as the withdraw
method.
Output
Sophie received a notification: 1000 deposited on JointAccount
Norah received a notification: 1000 deposited on JointAccount
If Sophie is not interested in receiving notifications, she can unsubscribe from the joint account as follows:
jointAccount.unsubscribe(firstAccountOwner);
jointAccount.deposit(5000);
Since Sophie has unsubscribed from the joint account, only Norah will receive the deposit notifications.
Output
Norah received a notification: 5000 deposited on JointAccount
Closing Notes #
The above example is a very simple example of the observer pattern but it can be used to build complex systems too.
The only dependency between the JointAccount
and AccountOwner
is the update
method. So, we can easily add new observers without changing the subject. Also, we can easily add new subjects without changing the observers.
Deciding on which pattern to use is not always easy and there are many variables to consider depending on the nature of the system. Further, the same system can be designed using different patterns. So, it is important to understand the pros and cons of each pattern and choose the right one for the system.
Some of the key points to remember about the observer pattern are:
- It helps to create loosely coupled system.
- It defines a one-to-many relationship between objects.
- It's main goal is to reduce dependencies between objects. The only information the subject knows about an observer is that it has some method that can be called when some event occurs or some state changes on the subject.
- It is used to build event-driven systems.
If you have any questions or feedback, please feel free to comment below.