Observer design pattern for beginners.

Problem statement –

Nowadays we all are very active on the internet. Many times we see educational web sites offering free learning with lots of topics. Different people will be interesting on various topics. And web site has millions of users. Now, if you think to design such kind software. How you will keep track of who likes which topics? If u apply simple math, billion users, one user can love as much topic as he/she wish to learn. And you have to send them regular updates related to those topics. Like new chapter has been added or modified on that topic. To all enrolled learners. User very from 1 to 1 million from topic to topic. 

Actually we can address this problem through observer design pattern, we will see how in this article.

Prerequisite –

All users shall have one unique identity. For example, email IDs, or say roll numbers, or employee ID, etc. All shall be uniquely identified.

You must have noticed. To register in any online forum, it ask your email ID. The reason is, all they are using is the observer design pattern. So once any update happens, they can send updates to all registered users. 

Why we need observer pattern –

It is nearly impossible to solve the problem as mentioned above without observer. There are many design patterns exist, which make designers' life easy. But if you don’t use them, you can still survive through implementing project one or another way, but you will pay huge maintenance cost at the end. 

But if there are some problem statements involving massive participation of different users and many topics. Not only in learning portals but in social networking sites, where you can be part of many groups. According to your interest. You are seeking update messages. As soon as anyone updates something on the registered groups. Then the only way to solve that complex problem, straightforwardly, is the observer design pattern.

About observer design pattern –

Observer design pattern works in implicit referencing. Implicit referencing – means; without knowing who the user is, and other information. It broadcast message to all registered users. 

Some people consider that observer works as mediator or kind of extension to mediator pattern. Forwarding messages to its proper destination, but when the number of users is in millions or in billion. Then we cannot use the mediator as we have to manually register all of them in the mediator class. And especially in this case where one person can be part of many topics. Complexity will go high, and it will become unmanageable code.

There are two ways of broadcasting messages in observer pattern –
  1. Parameterized – Where you pass full message to all registered users.
  2. Non-parameterized – Where you just send a link to the user. Mentioning that there are some updates, if you wish, please click on it.
So in an observer design approach. Whenever any new user subscribe to any new topic. First, we register his/her unique identity (email IDs, or say roll numbers, or employee ID, etc.). And put it in our database. Then if any changes occur on that particular topic. We go to our database and fetch all register user's unique identities. Send a notification mail to all of them.

Most broadcast and multicast models are designed based on observer design pattern.

UML Structure –

observer DP
UML structure of observer design pattern.

Example - 

observer DP
Example of observer design pattern (Click on picture to see zoom view)



Cpp code -




#include <iostream>
#include <map>
#include <set>
#include <string>
using namespace std;

typedef enum _SUBJECTS
{
  CPP,
  DESIGN
} SUBJECTS;

const char* SubjetcsList[] = { "CPP", "DESIGN" };

struct UserProfile
{
  string          Name;
  string          ID;
  string          MobileNumber;
  set<SUBJECTS>   Subjetcs;

  UserProfile(string& name, string& id, string& number): Name(name), ID(id), MobileNumber(number) {}
};

class Learn
{
  public:
    virtual void subscribe(shared_ptr<UserProfile> user) = 0;
    virtual void unSubscribe(string emailId) = 0;
    virtual void notify(string& user) = 0;
    virtual void updateContent(string& user, string& msg) = 0;
};

class Observer 
{
  public: 
    virtual void update(string& id, string& msg) = 0;
};

shared_ptr<Observer> GetObserver(SUBJECTS subject);

class CPlusPlus : public Learn
{
  private:
    map<string, shared_ptr<UserProfile> > mObservers;
    string CppContent;
  public:
    CPlusPlus(): CppContent("Welcome to CPP learning") {}
    void subscribe(shared_ptr<UserProfile> user)
    {
      mObservers.insert(pair<string, shared_ptr<UserProfile> >(user->ID, user) );
      user->Subjetcs.insert(CPP);
    }
    void unSubscribe(string emailId)
    {
      auto pSubscriber = mObservers.find(emailId);
      if (pSubscriber != mObservers.end())
      {
        mObservers.erase(pSubscriber);
      }
      else
      {
        cout << "This subscriber does not exist " << endl;
      }
    }
    void notify(string& fronUser)
    {
      for (auto& observer : mObservers)
      {
        if(observer.first != fronUser)  // don't send update to self.
          GetObserver(CPP)->update(observer.second->ID, CppContent);
      }
    }
    void updateContent(string& user, string& msg)
    {
      cout << "User " << user << "has updated  CPP materials " << endl;
      CppContent = msg;
      notify(user);
    }

};

class Design : public Learn
{
  private:
    map<string, shared_ptr<UserProfile> > mObservers;
    string DesignContent;
  public:
    Design(): DesignContent("Welcome to Design learning") {}
    void subscribe(shared_ptr<UserProfile> user)
    {
      mObservers.insert(pair<string, shared_ptr<UserProfile> >(user->ID, user));
      user->Subjetcs.insert(DESIGN);
    }
    void unSubscribe(string emailId)
    {
      auto pSubscriber = mObservers.find(emailId);
      if (pSubscriber != mObservers.end())
      {
        mObservers.erase(pSubscriber);
      }
      else
      {
        cout << "This subscriber does not exist " << endl;
      }
    }
    void notify(string& fronUser)
    {
      for (auto& observer : mObservers)
      {
        if (observer.first != fronUser)  // don't send update to self.
          GetObserver(DESIGN)->update(observer.second->ID, DesignContent);
      }
    }
    void updateContent(string& user, string& msg)
    {
      cout << "User " << user << " has updated Design materials " << endl;
      DesignContent = msg;
      notify(user);
    }
};

class CppObserver : public Observer 
{
  public:
    virtual void update(string& id, string& msg)
    {
      cout<< "Hi " << id << " check out new update on CPP - " << endl;
      cout << msg << endl;
    }
};

class DesignObserver : public Observer 
{
  public:
    virtual void update(string& id, string& msg)
    {
      cout << "Hi " << id << " check out new update on Design" << endl;
      cout << msg << endl;
    }
};
namespace
{
  shared_ptr<Learn>       pCpp;
  shared_ptr<Learn>       pDesign;
  shared_ptr<Observer>    pCppObserver;
  shared_ptr<Observer>    pDesignObserver;

  void Setup()
  {
    pCpp                = make_shared<CPlusPlus>();
    pDesign             = make_shared<Design>();
    pCppObserver        = make_shared<CppObserver>();
    pCppObserver        = make_shared<DesignObserver>();
  }
}
shared_ptr<Observer> GetObserver(SUBJECTS subject)
{
  if (subject == CPP)
    return pCppObserver;
  else if (subject == DESIGN)
    return pCppObserver;
}
shared_ptr<Learn> GetSubject(SUBJECTS subject)
{
  if (subject == CPP)
    return pCpp;
  else if (subject == DESIGN)
    return pDesign;
}
void EnrollForCPP(shared_ptr<UserProfile> user)
{
  GetSubject(CPP)->subscribe(user);
}
void EnrollForDesign(shared_ptr<UserProfile> user)
{
  GetSubject(DESIGN)->subscribe(user);
}
void Edit(shared_ptr<UserProfile> user, SUBJECTS subject, string content)
{
  auto user_subject = user->Subjetcs.find(subject);
  if (user_subject == user->Subjetcs.end())
  {
    cout << user->Name << " ! You have not subscribed subject - " << SubjetcsList[subject] << endl;
    return;
  }
  GetSubject(subject)->updateContent(user->ID, content);
}
shared_ptr<UserProfile> CreateUserProfile(string& name, string& id, string& number)
{
  return make_shared<UserProfile>(name, id, number);
}
/*
#######################################################################################
#########  __  __       _        ######################################################
######### |  \/  | __ _(_)_ __   ######################################################
######### | |\/| |/ _` | | '_ \  ######################################################
######### | |  | | (_| | | | | | ######################################################
######### |_|  |_|\__,_|_|_| |_| ######################################################
#######################################################################################
*/
int main(int argc, char** argv)
{
  Setup();
  string name = "Bob";
  string id = "Bob@gmail.com";
  string number = "1234561";
  shared_ptr<UserProfile> user1 = CreateUserProfile(name, id, number);
  EnrollForCPP(user1);
  EnrollForDesign(user1);

  string name_2 = "Max";
  string id_2 = "Max@gmail.com";
  string number_2 = "1234562";
  shared_ptr<UserProfile> user2 = CreateUserProfile(name_2, id_2, number_2);
  EnrollForDesign(user2);

  Edit(user1, CPP, "CPP 11 content has been added now");
  Edit(user1, DESIGN, "New currency design pattern has been added, check the web-page");

  Edit(user2, DESIGN, "New design pattern of pattern oriented software architecture has been added, check the web-page");
  Edit(user2, CPP, "CPP 14 content has been added now");
  return 0;
}

///////////////////////////////////////////////////////////////////-------------OutPut
/*
User Bob@gmail.comhas updated  CPP materials
User Bob@gmail.com has updated Design materials
Hi Max@gmail.com check out new update on Design
New currency design pattern has been added, check the web-page
User Max@gmail.com has updated Design materials
Hi Bob@gmail.com check out new update on Design
New design pattern of pattern oriented software architecture has been added, check the web-page
Max ! You have not subscribed subject - CPP
*/


Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page.

Comments

Popular posts from this blog

Non-virtual interface idiom (NVI)

Factory method design pattern for beginners.

Architectural patterns => Mud to structure => layers.