1
头图

I was very tired yesterday and didn't have the energy to write. Was shaken up early this morning. I didn't realize it was really shaking at first. I didn't feel it when I changed two positions. Just write. After a long while, I read the news and found out that it was indeed shocked. The last time CQ had a clear feeling was in 2008. I went downstairs with my baby and stood there. There is no real-time information at all. I don’t know how the situation will evolve. Shengdou Xiaomin wants to struggle. It is not available even if it is today, and it will not be different because the news is flying all over the sky.

Because the Chinese website is really a place full of garbage.

So the news bulletin I subscribed to is meaningless. Why, because the domestic Android machine is too dirty, so I always install a lot of tools to kill the background. As a result, the notifications are always crackling at 6 o'clock in the evening. There are dozens or hundreds of them. Fortunately, there are groups, and dozens of them can be erased with one stroke. It's not too tiring and can be tolerated.

Thank you for watching me talk, let's talk about the observer mode:

Observer Pattern

The observer pattern is a behavior pattern, it is a subscription-publishing mechanism. Objects can make announcements. When such announcement events occur, anyone who has registered observer status with the object will be able to receive notifications. Registering an identity means subscribing, and publishing when an event occurs.

There can be many observers to subscribe, and at this time there is an observer chain in the observed object.

Engineering problems

The usual C++ implementations focus on the model implementation of patterns, rather than on practical characteristics. So most of the observer pattern implementations you can see are case-based, and do not support cross-thread, asynchronous, and non-blocking.

Due to the obstructive one-way traversal feature of the observer chain, an irregular observer may suspend the notification chain of the event and further suspend the entire thread.

However, solving this problem is not easy-in most cases we rely on the agreement: the observer must do his own thing well, and must complete the observation quickly. If you want to completely solve this problem, you will need a timeout interrupt mechanism, but this will often make the implementation code of the entire observer mode ambiguous and complicated, which in fact makes this method infeasible.

This may be useful if your observer mode implementation supports asynchronous mode, but its problem is that the response delay of the event is unpredictable (by the CPU thread scheduling feature). There are two ways to trigger non-blocking: one is to start a thread when an event occurs to traverse the observer chain and call back in turn, and the other is to facilitate the observer chain and call back in a new thread. Both methods have their own characteristics, and you can evaluate them in actual implementation. In addition, the use of the coroutine standard library provided by C++20 helps to improve the response delay.

Is it a misunderstanding?

I haven't drawn UML diagrams for many years. In fact, I think this picture is not very useful. It is better to directly look at the code directly. You have to translate it in your head when you look at the picture. Look at the code. It seems that your brain is very proficient in CPU.

Did I miss something, or misunderstood something.

Scenes

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

The customer checks to see if the goods have arrived in the store. I will order a South China Morning Post. Order fresh milk every morning from the dairy company. and many more.

composition

Having said that (boring on uml), I still quote a picture:

观察者设计模式的结构

FROM: Observer design pattern
  1. Publisher (Publisher) will send interesting events to other objects. Events will occur after the publisher's own state changes or performs specific actions. The publisher contains a subscription structure that allows new subscribers to join and current subscribers to leave the list.
  2. When a new event occurs, the sender will traverse the subscription list and call the notification method of each subscriber object. This method is declared in the subscriber interface.
  3. subscriber (Subscriber) interface declares the notification interface. In most cases, this interface only contains one update update method. This method can have multiple parameters so that the publisher can pass the details of the event when it is updated.
  4. Specific subscribers (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 with concrete classes.
  5. Subscribers usually need some contextual information to properly handle updates. Therefore, the publisher usually passes some context data as the parameters of the notification method. The publisher can also pass itself as a parameter, so that the subscriber can directly obtain the required data.
  6. Client (Client) will create publisher and subscriber objects respectively, and then register publisher updates for subscribers.

accomplish

The C++17 brand-new realization of the observer mode mainly lies in these aspects:

  • Use smart pointers instead of the previous bare pointers, and at the same time fine and clear management rights
  • Allow different ways to add observers
  • Allow custom Observer type
  • Give priority to empty structures as event signals

image-20210916080003947

Core template observable and observer

A default observer base class template is recommended to provide you with a basic prototype. Your observer class should be derived from this template. Unless you intend to define the interface yourself (but, to a large extent, the necessity of self-definition is infinitely close to zero, because the observable template requires an Observer to have an interface such as observe(subject const&)

As for the observable template itself, it contains'+=' and'-=' operator overloads, so you can use a more semantic encoding method.

The code is as follows (refer to 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

In the current implementation, the template parameter Managed of observable is useless, and the function of hosting observers has not yet been implemented, so you always have to manage each observer instance by yourself. In the observable, only the weak_ptr of the observer is included, which paves the way for the addition of asynchronous capabilities in the future, but its current use appears to be of little use.

I have said a lot in the front, but the code of the core class template is like this when it is implemented, not too much.

test case

The method used is:

  • Declare the event signal as a structure, you can include the necessary load in the structure, so as to use a single structure to carry different event signals
  • But observable does not support you to provide event signals of multiple structure types
  • Observables need to be derived from observable
  • The observer uses make_shareable create and register to the observable object

The sample 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;
}

Store is an observable object.

Customer as an observer, store += c , and deregistered store -= c

In the right place, store.emit() emits an event signal, and then all observers will receive the signal, and then explain it as it should be.

Note the degradation of smart pointers:

  • Must use Store::observer_t_shared c = std::make_shared<Customer>(); , because the'+=' and'-=' operators can recognize the type hicc::util::observable<event>::observer_t_shared
  • If you use auto c = std::make_shared<Customer>() , they cannot be deduced by'+=' or'-=', the compilation will not be completed
  • CRTP technology can be used to solve this problem, but it is not necessary-you can complain, I might be motivated

legacy problem:

  • There is no mechanism to prevent observers from re-registering. It is not difficult to add it, but we feel that you should not write the code for repeated registration, so we don’t care whether it is repeated or not, you come~

Epilogue

We have not been able to solve the difficult problem. The difficult problems raised above can only be solved by an enhanced version of the observer mode-Rx/ReactiveX-in reality. But ReactiveX is not a pure subscription model at all, and the threshold is too high.

So, maybe, next time I consider making a simpler Rx, you know that ReactiveX already has RxCpp, but we may make a simple version of Rx, and the main purpose is to add asynchronous capabilities to the observer mode. As for Those operators gave up.

Subscriber mode, or observer mode, has a well-known implementation: Qt's Signal-Slot mechanism. This kind of thing depends on Qt's QObject, which provides a mechanism that can be triggered by signal after connect. It is almost equivalent to the observer mode, but emphasizes the concept of sender and receiver, which is implemented for the observer mode in most cases. That may not be necessary. However, the signal slot mechanism for developers before C++11 provides the ability to call back the slot function with parameters and no correlation. This was something that everyone could not do at the beginning, even after C++11, because the template changes to participate in perfect forwarding Sometimes the grammar is not perfect, and you have to wait until C++14/17 to have a comprehensive surpass. So now, the slot mechanism has lost its allure and is just sticking to it. In practical applications, unless you are using qt, it is easy to make an observable casually.


BTW, a digression, since there was a barrage, uncivilized behaviors in tourist attractions are no longer heard. For example, in Alan's 94 Golden Melody concert, there were a lot of people who checked in yesterday and the day before yesterday. OK. This is the contribution of the barrage, right?

If I do open source, do I also contribute to the world? Everyone still likes to be recognized.

:end:


hedzr
95 声望19 粉丝