I was too tired to write yesterday. I was shaken up early this morning. I didn’t realize it was really shaking at first. I changed two positions and didn’t feel it. After a long time, watching the news just know did the earthquake, the last time the CQ has obvious feeling is 08, holding her down the stairs to stay standing, no real time information, don’t know how things will evolve, differing wang want to also do not have to struggle, even today, in fact, are not news flying are different.

Because Chinese is really a place full of garbage ah.

That’s why my subscription to the earthquake newsletter is meaningless. Why, because domestic Android phones are too dirty, so I always install a lot of tools to kill background. Results kill notice always at 6 o ‘clock in the evening to dozens of hundreds of crackling, fortunately there are groups, a row can erase dozens of, not tired, can endure.

Thanks for watching me prattle on, let’s move on to the observer model:

Observer Pattern

The observer pattern is a behavior pattern. It is a subscription-publish mechanism. Objects can publish announcements, and those who have registered observer status with the object will be notified when the announcement event occurs. Register identity to subscribe, event to publish.

It is possible to subscribe to many observers, holding a chain of observers in the observed object.

Engineering difficulties

In general, C++ implementations focus on modeling patterns rather than on practical qualities. So most implementations of the observer pattern you can see are case-based and do not support cross-threading, asynchronous, and non-blocking.

Due to the blocking, one-way traversal nature of the observer chain, an unruly observer may suspend the notification chain of the occurrence of an event, and further suspend the entire thread.

However, solving this problem is not easy — in most cases we rely on conventions: the observer must do his or her job properly, and the observation must be done quickly. A complete solution to this problem would require a timeout break mechanism, but this would often make the implementation code for the entire observer pattern vague and complex, making this approach virtually impossible.

This can be useful if your observer mode implementation supports asynchronous mode, but the problem is that the response time of events is unpredictable (due to the SCHEDULING nature of CPU threads). There are two ways to do this: start a thread when the event occurs in order to traverse the observer chain and call back in turn, or facilitate the observer chain and call back in a new thread. Both approaches have their own characteristics and you can evaluate them as they are actually implemented. In addition, the adoption of the coroutine standard library provided by C++20 helps improve response latency.

Is misunderstanding?

I haven’t drawn a UML diagram in years. Actually, I don’t think this graph is very useful. It’s not as intuitive as looking at the code directly. To look at the graph, you have to translate it in your head.

Maybe I missed something, or maybe I misunderstood something.

scenario

The observer pattern is so easy to understand that there is no need to design an appropriate scene to explain it.

The customer looked at the store to see if the goods had arrived. I’ll get a subscription to the South China Morning Post. Order fresh milk every morning from the dairy company. And so on.

composition

That being said (Boring on UML), let me quote a diagram:

FROM: Observer design patterns

  1. Publishers send noteworthy events to other objects. Events occur after the publisher’s own state changes or certain actions are performed. Publishers include a subscription architecture that allows new subscribers to join and current subscribers to leave the list.
  2. When a new event occurs, the sender iterates through the subscription list and invokes the notification method of each subscriber object. This method is declared in the subscriber interface.
  3. The subscriberSubscriber interface Specifies the notification interface. In most cases, this interface contains only oneupdateUpdate method. This method can have multiple parameters that enable the publisher to pass the details of the event at update time.
  4. Concrete Subscribers can perform some operations in response to the publisher’s notification. All concrete subscriber classes implement the same interface, so publishers do not need to be coupled to concrete classes.
  5. Subscribers usually need some context information to properly process updates. As a result, publishers typically pass some context data as parameters to the notification method. Publishers can also pass themselves as parameters, allowing subscribers to get the data they want directly.
  6. The Client creates publisher and subscriber objects, respectively, and registers publisher updates for subscribers.

implementation

The new C++17 implementation of the observer mode mainly focuses on these aspects:

  • Smart Pointers are used instead of bare Pointers, and management rights are refined
  • Allows different methods to add observers
  • Custom Observer types are allowed
  • Preference is given to empty structures as event signals

The core templates Observable and Observer

A default recommended Observer base class template provides you with basic construction prototypes. Your observer class should derive from this template. Unless you’re going to define your own interface (though, for the most part, the necessity of doing so approaches zero, since the Observable template requires an Observer to have an interface like Observe (Subject const&)).

The Observable template itself includes the ‘+=’ and ‘-=’ operator overloads, so you can code more semantically.

The code is as follows (see HZ-common.hh) :

 namespace hicc::util {
 ​
   template<typename S>
   class observer {
     public:
     virtual ~observer() {}
     using subject_t = S;
     virtual void observe(subject_t const &e) = 0;
   };
 ​
   template<typename S, typename Observer = observer<S>, bool Managed = false>
   class observable {
     public:
     virtual ~observable() { clear(); }
     using subject_t = S;
     using observer_t_nacked = Observer;
     using observer_t = std::weak_ptr<observer_t_nacked>;
     using observer_t_shared = std::shared_ptr<observer_t_nacked>;
     observable &add_observer(observer_t const &o) {
       _observers.push_back(o);
       return (*this);
     }
     observable &add_observer(observer_t_shared &o) {
       observer_t wp = o;
       _observers.push_back(wp);
       return (*this);
     }
     observable &remove_observer(observer_t_nacked *o) {
       _observers.erase(std::remove_if(_observers.begin(), _observers.end(), [o](observer_t const &rhs) {
         if (auto spt = rhs.lock())
           return spt.get() == o;
         return false;
       }), _observers.end());
       return (*this);
     }
     observable &remove_observer(observer_t_shared &o) {
       _observers.erase(std::remove_if(_observers.begin(), _observers.end(), [o](observer_t const &rhs) {
         if (auto spt = rhs.lock())
           return spt.get() == o.get();
         return false;
       }), _observers.end());
       return (*this);
     }
     observable &operator+=(observer_t const &o) { return add_observer(o); }
     observable &operator+=(observer_t_shared &o) { return add_observer(o); }
     observable &operator-=(observer_t_nacked *o) { return remove_observer(o); }
     observable &operator-=(observer_t_shared &o) { return remove_observer(o); }
 ​
     public:
     /**
       * @brief fire an event along the observers chain.
       * @param event_or_subject 
       */
     void emit(subject_t const &event_or_subject) {
       for (auto const &wp : _observers)
         if (auto spt = wp.lock())
           spt->observe(event_or_subject);
     }
 ​
     private:
     void clear() {
       if (Managed) {
       }
     }
 ​
     private:
     std::vector<observer_t> _observers;
   };
 ​
 } // namespace hicc::util
Copy the code

In current implementations, the Observable template parameter, Managed, is useless. Observer hosting is not yet implemented, so you always have to manage each observer instance yourself. In Observable, weak_ptr is only included in the observer, which fosters the addition of asynchronous capability in the future, but it is not very useful at present.

Much has been said in the previous section, but this is the code for the core class template when it is implemented, not much.

test case

The methods used are:

  • By declaring the event signal as a structure, you can include the necessary payload in the structure to use a single structure to carry different event signals
  • An Observable does not allow you to provide multiple struct types of event signals
  • Observables need to be derived from Observables
  • Observer utilizationmake_shareableCreate and register an observable

Example code is as follows:

namespace hicc::dp::observer::basic { struct event {}; class Store : public hicc::util::observable<event> {}; class Customer : public hicc::util::observer<event> { public: virtual ~Customer() {} bool operator==(const Customer &r) const { return this == &r; } void observe(const subject_t &) override { hicc_debug("event raised: %s", debug::type_name<subject_t>().data()); }}; } // namespace hicc::dp::observer::basic void test_observer_basic() { using namespace hicc::dp::observer::basic; Store store; Store::observer_t_shared c = std::make_shared<Customer>(); // uses Store::observer_t_shared rather than 'auto' store += c; store.emit(event{}); store -= c; }Copy the code

Store is an observable.

Customer, as an observer, registers with store += c and unregisters with store -= c.

In place, store.emit() emits an event signal, which all observers will receive and interpret as they wish.

Note the degradation of the smart pointer:

  • You must use theStore::observer_t_shared c = std::make_shared<Customer>();, because what the ‘+=’ and ‘-=’ operators recognize ishicc::util::observable<event>::observer_t_sharedtype
  • If you useauto c = std::make_shared<Customer>()They cannot be derived by ‘+=’ or ‘-=’, and compilation will fail
  • CRTP technology can be considered to solve this problem, but it’s not really necessary – you can complain and I might be motivated

Remaining issues:

  • There is no mechanism to prevent observers from registering twice. It’s not difficult to add, but we don’t think you should write code that registers twice, so we don’t care about duplicates, you do

Epilogue

We failed to solve the problem. The problems mentioned above can only be solved by a real-world enhanced version of the Observer mode, Rx/ReactiveX. ReactiveX, however, is not a pure subscription model at all, and the bar is too high.

So, or, maybe, next time consider doing a simpler Rx, you know ReactiveX already has RxCpp, whereas we might do a simpler VERSION of Rx, where the main goal is to add asynchrony to the observer mode, and the operators are dropped.

There is another well-known implementation of the subscriber or observer pattern: Qt’s signal-slot mechanism. This kind of thing relies on Qt’s QObject to provide a mechanism that can be triggered by signal after connect. It is almost equivalent to observer mode, but emphasizes the concepts of sender and receiver, which may not be necessary for most observer mode implementations. However, for developers before C++11, the signal slot mechanism provides the ability to call back slot functions with parameters and no correlation, which was not possible at the beginning. Even after C++11, due to the template change and perfect forwarding, sometimes the syntax is not perfect, but also after C++14/17 can be fully beyond. So for now, slot has lost its allure and is just clinging to it, whereas in practice, unless you’re using Qt, it’s pretty easy to just be an Observable.


BTW, as an aside, since the bullet screen was introduced, the uncivilized behavior of visiting tourist attractions is no longer heard. For example, there were many people clocking in Alan’s 1994 Golden Song concert yesterday and the day before yesterday, which is also quite good. That’s what the barrage does, isn’t it?

Am I contributing to the world by doing open source? Everyone still likes to be recognized.

🔚