If we were asked which the easiest and most widely used design pattern is, it would have to be Observer design pattern. This is one of the interviewer’s most favourite topics and crops up in interviews very frequently. Let’s start with providing you with some examples to make you understand the concepts properly.
Say you are logged in into multiple devices in an application like Gmail or you are receiving messages on Slack. It could be your laptop, phone, tablet etc. Now as soon as you receive a message, all these devices have to be notified at once.
How do you think that happens?
This is a very common problem. Consider any system – whenever one change happens, we have to do some logging and we may have to take some action against it. Maybe we have to do some monitoring and notify some other services. So just on the basis of a single change, so many things have to happen.
Another example of this, which we’ll in fact be using in our code, is group messaging. Whenever someone messages, everyone gets notified. How does that happen?
All of these problems can be solved by using the Observer design pattern. Hopefully by now, you have got a hint for what purpose it serves.
In short, whenever there is one data change happening, a lot of people have to be notified and a lot of things have to be done. Observer design pattern serves all these purposes.
Some people might use different jargon, but this is the main concept. So some people might say there is a publisher who is publishing some change and there are some subscribers who are subscribed to this change, so that whenever the publisher makes some change, the publisher is notified. Now there are other things like these subscribers can subscribe or unsubscribe as per their will and they need to be notified, but essentially there is one process happening. There is a single event that occurs, with multiple things happening in response to it, and all the people have to be notified about the change in this particular thing.
Another jargon that people use is that there is a subject and there are observers who are observing that subject. So whenever a change happens in the subject, the observers are notified.
Let’s try to understand the exact working behind this pattern.
There is a Subject, and within which there is a collection of observers, having many observers added to it. Then there is also registerObserver(), using which you can add a new subscriber or an observer. There’s also unregisterObserver(), using which you can remove a subscriber or unsubscribe. And lastly, there is notifyObservers(), through which notifications will be sent to all the subscribers, whenever any changes are made.
So what is the purpose of the Observer?
The observer has to take a predetermined action whenever they are notified. These actions can be to send emails, messages, or push notifications. Whenever some data is sent from the subject, the observer has to take actions based on it.
If you still haven’t understood it properly, then don’t worry. We will try to understand it with the help of the code!
Implementation
Let’s first see what the code looks like from the client’s perspective.
- We will first form a new Group followed by three Users as user1, user2 and user3. We will pass user IDs as 1, 2 and 3 to identify different users. Alright, so we have three users and a group.
- Now we need to add the users to the group in order to make them subscribed. For that we will write a subscribe()
- And whenever we send a message, all its users have to be notified. For that to happen, we all add a notify() function in the group itself.
- In order to check the subscribed/unsubscribed part as well, we’re going to add one more bit. Let’s say we want to unsubscribe user1, so we write an unsubscribe() function and send a group notification to check how many users receive the new message.
This is how our code will look on the client’s side.
int main() {
Group* group = new Group;
User* user1 = new User(1);
User* user2 = new User(2);
User* user3 = new User(3);
group->subscribe(user1);
group->subscribe(user2);
group->subscribe(user3);
group->notify("new msg");
group->unsubscribe(user1);
group->notify("new new msg");
return 0;
}
- Now let’s check out the implementation of the observer design pattern.
- For that, we will first create these users and we will use an interface class so that we can keep the list of subscribers in that interface. Let’s call it the ISubscriber class. In it, we write virtual function notify(), which we make a pure virtual function by equating to 0. Our ISubscriber class will now become a pure interface class, and all the classes that inherit from the subscriber will have to implement the virtual function notify(). This is the behaviour we want all the subscribers to inherit – all the subscribers should get the update or the notification regarding any change, so that they can take any action. The actions can differ depending upon the subscriber.
- In this case, our subscribers are our users, so we need to create a User class as shown below, which will inherit from the class ISubscriber. As earlier sated, we’ve used user IDs just to distinguish the users, so let’s create a private variable userId. And let’s also define a public constructor that takes the userId parameter to create an object. Since all these users are subscribers, they have to implement the notify() function.
- In order to implement the notify function, we will write a void function notify() inside the User class and pass a string parameter named msg. Inside the function, we will print a statement indicating that this specific user has received the passed message.
So that’s all about the code we need to write for our users/subscribers.
class ISubscriber{
public:
virtual void notify(string msg) = 0;
};
class User : public ISubscriber {
private:
int userId;
public:
User(int userId) {
this->userId = userId;
}
void notify(string msg) {
cout<<"User "<<userId<<" received msg "<<msg<<endl;
}
};
- Moving on to the group, we will create a class Group which will contain the list of all the subscribers. Since we are going to have a list, we will have to include the corresponding header file, #include<list>.
- As we have our interface, we can make the list of Users easily in the private section of the class. In the public section, we will need to have everything needed from the client’s perspective – the subscribe(), unsubscribe(), and notify() functions.
- We will create a subscribe() function which takes a user as the parameter, and within the function we add the user to our list of users. Similarly, we will create the unsubscribe() function which will remove the passed user from the list of users. Finally, in the notify() function, we will have to pass a message to it. For each user in the list of users, we call the notify() function and pass the message to it as shown below.
class Group {
private:
list<ISubscriber*> users;
public:
void subscribe(ISubscriber* user) {
users.push_back(user);
}
void unsubscribe(ISubscriber* user) {
users.remove(user);
}
void notify(string msg) {
for(auto user: users)
user->notify(msg);
}
};
Conclusion:
When we run the output for the client-side function we wrote at the start, we can see that at first, all three users – user1, user2, user3 – received a new message. After that we unsubscribed user1 and hence, only user2 and user3 were notified with new message.
This is how we can dynamically change the number of users, and notify the users we want to. You can see that in the code clearly and match it with your terminal output.
That’s how easy and useful the Observer design pattern is, and we hope you get to successfully use it in your own coding adventures!
Would you like to watch a video walking you through this Observer design pattern implementation? Check out our video on the official YouTube channel!