[C++11] improve our design pattern -- observer pattern

Observer Pattern mainly solves that when there is a one to many relationship between objects, when an object is modified, it will automatically notify other objects that depend on it. In the design pattern, the Observer Pattern belongs to the behavior pattern.

1 Classic observer mode

In books related to design patterns, the following figure is mostly used when introducing observer patterns:

As mentioned above, the above class diagram is a simple observer mode. The above class description is as follows:

  • Subject: subject, which is popularly understood as the publisher of information, provides three interfaces: adding an observer, deleting an observer, and message notification;
  • ConcreteSubject: a specific Subject, a derived class of Subject, which contains two interfaces, setting and obtaining status;
  • Obersver: observer, which provides a notification interface.
  • ConcreteObersver: a derived class of Onersver, which implements the interface of the base class and maintains a reference to the ConcreteSubject object.

The code to implement the above observer mode is as follows:

class Observer{
public:
    virtual void Update(int) = 0;
};
 
class Subject{
public:
    virtual void Attach(Observer *) = 0;
    virtual void Detach(Observer *) = 0;
    virtual void Notify() = 0;
};
 
class ConcreteObserver : public Observer{
public:
    ConcreteObserver(Subject *pSubject) : m_pSubject(pSubject){};
    void Update(int value){
        cout << "Message notification received:" << value << endl;
    }
private:
    Subject *m_pSubject;
};
 
class ConcreteSubject : public Subject{
public:
    void Attach(Observer *pObserver);
    void Detach(Observer *pObserver);
    void Notify();
    void SetState(int state){
        m_iState = state;
    }
    
    void GetState(){
        return m_iState;
    }
private:
    std::list<Observer *> m_ObserverList;
    int m_iState;
};
 
void ConcreteSubject::Attach(Observer *pObserver){
    m_ObserverList.push_back(pObserver);
}
 
void ConcreteSubject::Detach(Observer *pObserver){
    m_ObserverList.remove(pObserver);
}
 
void ConcreteSubject::Notify(){
    std::list<Observer *>::iterator it = m_ObserverList.begin();
    while (it != m_ObserverList.end()){
        (*it)->Update(m_iState);
        ++it;
    }
}

As shown in the above code, a classic observer mode is implemented, but in actual use, this implementation method is not flexible and has many limitations. The two most obvious limitations are:

  • Inheritance relationship needs to be used, and there are many disadvantages of inheritance. For example, inheritance relationship will cause redundant code in derived classes, reduce the flexibility of code, and need to implement the methods defined in the parent class; The coupling with the base class is too strong. Once inheritance is used, the derived class has all the methods in the parent class.
  • The interface definition method between the subject and the observer is fixed, which can not adapt to the changes of new interaction methods brought about by the subsequent business development.

2 improved observer mode

In order to solve the problems in the classical observer mode, you can use the new language features provided in C++11, such as parameterizing the message notification interface and using std::function binding to solve the class inheritance problem, and eliminate the impact of interface changes through perfect forwarding and variable parameter templates. The improved observation mode does not need to inherit the base class when there are new observers. Just add an event type.

In this code implementation, we do not want the newly added event types to be copied. Therefore, during implementation, the = default and = delete identifiers will be used to limit the special functions of the class. The improved observer mode code is as follows:

class NonCopyable{
protected:
    NonCopyable()=default;
    ~NonCopyable()=default;
    NonCopyable(const NonCopyable &)=delete;
    NonCopyable & operator =(const NonCopyable &) = delete;
};

template <typename Func>
class Event:NonCopyable{
public:
    Event()=default;
    ~Event()=delete;
    //Register observers and support right value references
    int Attach(Func && f){
        return Assign(f);
    }
     //Register observers and support lvalue references
    int Attach(Func & f){
        return Assign(f);
    }
    //Remove observer
    void Deatach(int iKey){
        m_mapOberserve.erase(iKey);
    }
    //Notification interface
    template <typename... Args>
    void Notify(Args&&... args){
        for(auto& it:m_mapOberserve)
        {
            it.second(std::forward<Args>(args)...);
        }
    }
private:
    template <typename F>
    int Assign(F && f){
        int k = m_iObersevre++;
        m_mapOberserve.emplace(k,std::forward<F>(f));
        return k;
    }
    
    int m_iObersevre = 0;
    std::map<int,Func> m_mapOberserve;
};

As shown in the above code, the observer mode after using C++11 eliminates the code coupling caused by class inheritance by maintaining a list of generic functions, so that only the observer needs to be passed in to call the function. The notification interface also realizes the input of any parameter through the variable parameter template, which eliminates the impact of the notification interface not adapting to the business development and change.

Posted by manchuwok on Mon, 15 Nov 2021 23:32:56 -0800