Observer pattern

 Introduction:

Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

How to use,

  1. Differentiate between the core (or independent) functionality and the optional (or dependent) functionality.
  2. Model the independent functionality with a "publisher/subject" abstraction.
  3. Model the dependent functionality with an "observer/listener" hierarchy.
  4. The publisher is coupled only to the Observer base class.
  5. The client configures the number and type of Observers.
  6. Observers register themselves with the publisher.
  7. The publisher broadcasts events to all registered Observers.
  8. The publisher may "push" information at the Observers, or, the Observers may "pull" the information they need from the Subject.

UML class diagram:




Example,

Design a travel agency system to inform the passenger about bus arrival or delay notification system.
There are two different notification methods are available. One is SMS , another one is Email notification. Notifications will send to the customer based on the preference.

Class Diagram:



Sourcecode: https://github.com/krishnaKSA/design_patterns_cplusplus/blob/main/ObserverPattern/OberverPattern.hpp

Step 1 - Publisher interface class :

    //Publisher Interface class
    class IPublisher
    {
        public:
        virtual void addListener(eNotifyType type, IListener* listener) = 0;
        virtual void removeListener(eNotifyType type, IListener* listener) = 0 ;
    };

Step 2 - Listener interface class:

    //Listener Interface class
    class IListener
    {
        public:
        virtual void update(CMessage* message) = 0;
    };

Step 3 - Define the listener class :

The class which requires the state change update or need data from other classes will inherit from the IListener interface class.
This class provide the implementation of the override function declared in the listener interface class, and
register itself to publisher class in the constructor.

    class CEmailListener : public IListener
    {
        public:
        CEmailListener()
        {
            CPublisher::getPublisher()->addListener(eNotifyType::eEmail, this);
        }
        void update(CMessage* message) override
        {
            cout<<"In EMAIL "<<message->getMessage()<<endl;
        }
    };

    class CSMSListener : public IListener
    {
        public:
        CSMSListener()
        {
            CPublisher::getPublisher()->addListener(eNotifyType::eSMS, this);
        }
        void update(CMessage* message) override
        {
            cout<<"In SMS "<<message->getMessage()<<endl;
        }
    };

Step 4: Publisher class:

  • Publisher class has list of listener objects.

                std::unordered_map<eNotifyType, std::vector<IListener*>> listenerList;

  • Publisher class provides method to add listeners.

        void addListener(eNotifyType type, IListener* listener) override
        {
            //check
            if((eNotifyType::eEmail == type) || (eNotifyType::eSMS == type))
            {
                auto iter = listenerList.find(type);
                if( iter != listenerList.end())
                {
                    auto list = iter->second;
                    list.push_back(listener);
                }
                else
                {
                    std::vector<IListener*> tempList;
                    tempList.push_back(listener);
                    listenerList[type] = tempList;
                }
            }
            else{
                std::cerr<<__FUNCTION__<<" wrong type "<<type<<endl;
            }
        }

  • Publisher class provides method to remove listeners.

        void removeListener(eNotifyType type, IListener* listener) override
        {
            if(nullptr != listener)
            {
                auto iter = listenerList.find(type);
                if( iter != listenerList.end())
                {
                    auto list = iter->second;
                    size_t index = 0;
                    for(auto listenerId:list)
                    {
                        if(listenerId == listener)
                        {
                            list.erase(list.begin()+index);
                            break;
                        }
                        index++;
                    }
                    listenerList[type] = list;
                }
            }
        }

  • Send notification to the listeners

        void notifyListeners(eNotifyType type, CMessage* message)
        {
            auto iter = listenerList.find(type);
            if( iter != listenerList.end())
            {
                for(auto listener:iter->second)
                {
                    listener->update(message);
                }
            }
            else
            {
                cout<<"no listners for type = "<<type<<endl;
            }
        }

Travel agency:

This class provides the function to trigger the notification when the bus delay to arrive.
    class CTravelAgency
    {
        private:
        std::shared_ptr<CPublisher> publisher;

        public:
        CTravelAgency()
        {
            publisher = CPublisher::getPublisher();
        }

        void busDelayed()
        {          
            string text = "bus delayed for 2 hours";
            CMessage* msg = new CMessage;
            msg->setMessage(text);
            publisher->notifyListeners(eNotifyType::eEmail, msg);
            publisher->notifyListeners(eNotifyType::eSMS, msg);
        }
    };

Sample client code:

    void clientCode()
    {
        CSMSListener* smsListener = new CSMSListener;
        CEmailListener* emailListener = new CEmailListener;

        CPublisher::getPublisher()->printListener();
        CTravelAgency* agency = new CTravelAgency;
        agency->busDelayed();

        CPublisher::getPublisher()->removeListener(eSMS, smsListener);
        agency->busDelayed();
    }