Observer

Dated Sep 28, 2021; last modified on Sun, 12 Feb 2023

notes that , the primary source, glosses over nitty-gritties. e.g. the “message queue” server. Maybe reading through these will provide a larger perspective to design decisions made in Chromium regarding observers.

Rant: some of willchan’s thoughts on WeakPtr, for those who care to read criticizes the observer pattern for murking dependency chains. Investigate more in this regard.

Intent

A one-to-many dependency between object so that when one object (subject) changes, all its dependents (observers) are notified and update automatically.

A meta-point is that while we could achieve consistency by making the classes tightly coupled, we don’t want to do that because tight coupling reduces reusability.

An advantage of tight coupling (e.g. adding would-be-observers as member variables) is that the compiler can detect errors at compile time and optimize the code at the CPU instruction level. So for performance-senitive code, the observer design pattern may not be apt.

What are the tradeoffs between the observer design pattern, and having the observer pass callbacks to the subject?

posits that callback patterns are more tightly coupled than a subject/observer pattern. Furthermore, callbacks are typically meant to be short-lived.

Need to think about this some more. Not totally sold.

Sample Code

An abstract class defines the Observer interface:

class Subject;

class Observer {
 public:
  virtual ~Observer();

  // To support multiple subjects for each observer, we pass
  // `changed_subject`.
  virtual void Update(Subject* changed_subject);

 protected:
  Observer();
};

An abstract class defines the Subject interface:

class Subject {
 public:
  virtual ~Subject();
  virtual void Attach(Observer*);
  virtual void Detach(Observer*);
  virtual void Notify();

 protected:
  Subject();

 private:
  // Notice that we do not make assumptions on the number of observers.
  //
  // We also do not make assumptions on the concrete classes. As long as
  // the class implements the Observer interface, we're good! The
  // coupling is abstract and minimal.
  List<Observer*> observers_;
};

void Subject::Attach(Observer* observer) {
  observers_.Append(observer);
}

void Subject::Detach(Observer* observer) {
  observers_.Remove(observer);
}

void Subject::Notify() {
  // In this design (pull model), we do not tell the observers what
  // exactly changed in `this`. It is up to the concrete observer to
  // query the concrete subject. The concrete observer can also choose
  // to ignore the notification.
  for (Observer* observer : observers_) observer->Update(this);
}

Another design gotcha is observers_ is observers_ getting modified while it is being looped over in Subject::Notify. For some operations, the iterator may get invalidated.

In Chromium, is designed to be resistant to same-thread modifications of the underlying std::vector. approaches the problem in a multi-threaded context, with the tradeoff being that notifications get silently dropped if an observer is removed on one thread, while another thread is notifying observers.

At the very least, be aware that the problem exists.

An example concrete Subject, ClockTimer, that stores and maintains the time of day:

class ClockTimer : public Subject {
 public:
  ClockTimer();
  virtual int GetHour();
  virtual int GetMinute();
  virtual int GetSecond();

  // Methods that eventually call `Notify()` should be labelled as such,
  // so that callers can be aware of the cost of a seemingly innocuous
  // operation.
  void Tick();
};

// Called by an internal timer at regular intervals.
void ClockTimer::Tick() {
  // Update internal-time keeping state.
  // ...
  Notify();
}

An example concrete Observer, DigitalClock, that displays the time of day.

class DigitalClock: public Widget, public Observer {
 public:
  DigitalClock(ClockTimer*);
  virtual ~DigitalClock();

  // From Observer
  void Update(Subject*) override;

  // Widget
  void Draw() override;

 private:
  ClockTimer* subject_;
};

DigitalClock::DigitalClock(ClockTimer* subject) {
  subject_ = subject;
  subject_->Attach(this);
}

DigitalClock::~DigitalClock() {
  subject_->Detach(this);
}

void DigitalClock::Update(Subject* changed_subject) {
  if (changed_subject == subject_) Draw();
}

void DigitalClock::Draw() {
  // Get the new values from the subject.
  int hour = subject_->GetHour();
  int minute = subject_->GetMinute();
  int second = subject->GetSecond();

  // Draw the digital clock.
}

A possible source of bugs is an observer that gets destroyed without removing itself from its subjects' observer lists. A subject will then try to notify an observer that has already been destroyed, leading to a use-after-free (UAF) bug. In Chromium, a couple of safeguards exist to avoid this UAF.

  1. Observers subclass from CheckedObserver, and subjects store the observers in an ObserverList. Iterating over a destroyed Observer in an ObserverList leads to a crash, which avoids the more severe UAF.

  2. Concrete observers hold a member variable, ScopedObservation or ScopedMultiSourceObservation, that removes registered observation(s), if any, when the observer is destroyed. That way, observers need not explicitly remove themselves from the subject(s)’s observer list.

suggests that the subject hold weak references. Why is this not the case in Chromium with ? Performance reasons? Too many observers that don’t pass WeakPtr?

Usage:

ClockTimer* timer = new ClockTimer();
DigitalClock digital_clock = new DigitalClock(timer);
A sample UML class and sequence diagram for the Observer design pattern. Courtesy: Wikipedia.
A sample UML class and sequence diagram for the Observer design pattern. Courtesy: Wikipedia.

Commentary on Design Decisions

Communication Flow

If concrete_observer calls concrete_subject->SetState(new_state), then concrete_observer should hold off making any internal updates until it receives an update notification from concrete_subject. This deferral is important because there may be other objects that call concrete_subject->SetState(new_state).

We also don’t want to do two internal updates in concrete_observer as the first one is jumping the gun. Also, what if concrete_subject rejects the new_state? Overly eager updates could make concrete_observer inconsistent.

Sure, but this design feels weird. Why would an observer be modifying the state of the subject? When I think of the relationship between a subject and an observer, I think of the observer being more or less passive. seems to hold the observer-is-passive-with-regard-to-updates idea too.

Who calls Subject::Notify? If state-setting ops on Subject call Notify, we might have several consecutive ops cause several consecutive updates, which may be inefficient. If clients are responsible, then errors are likely since some clients might forget to do it.

Avoiding Dangling References

Deleting a subject should not produce dangling references in its observers. One solution is have the subject notify its observers when as it is deleted, so that observers can reset their reference to it. Deleting the observers is not a solution because the observers might be referenced elsewhere, e.g. listening to other subjects.

Why did Chromium opt for a ScopedObservation and ScopedMultiSourceObservation that don’t expose the subject(s)?

Maintaining Consistency of the Subject

We also need to ensure that the subject is self-consistent before notification. For example, this code is buggy:

void MySubject::Operation(int new_value) {
  BaseClassSubject::Operation(new_value);
  // Trigger notification.
  my_internal_value_ += new_value;
  // Update subclass state (too late!)
}

One solution is to send notifications from template methods in the abstract Subject class, e.g.

void Text::Cut(TextRange r) {
  ReplaceRange(r);  // Redefined in subclasses.
  Notify();
}

Specificity/Efficiency of Update Protocols

There’s a tradeoff on the specificity of Update protocols. In the push model, the subject sends detailed information about the change, while in the pull model, the subject sends out the most minimal notification, and the observers must explicitly ask for details. The push model makes observers less re-usable, while the pull method may be inefficient.

Allowing observers to register for specific events of interest can improve update efficiency. An API like this one is a possible solution:

void Subject::Attach(Observer*, Aspect& interest);

void Observer::Update(Subject*, Aspect& interest);

A further step could be the Subject only sending notifications to observers that are interested in the Aspect that has been affected by the subject’s state change.

Alternatively, a common pattern that I see in Chromium is subclassing the base Observer interface, and providing more specific APIs, e.g. ClockTimerObserver::OnHourChanged(ClockTimer* timer, int new_hour).

In some cases, the updates may inundate the observer, e.g. the subject’s changing state several times a second. The observer may incorporate a timer to represent the approximate state of the model at a regular interval.

Orchestrating Complex Relationships Between Subjects and Observers

The dependency relationships between subjects and observers may be complex, e.g. an operation that involves changes to several interdependent subjects, and there is a need to ensure that observers are notified only after all subjects have been modified, that a ChangeManager is useful:

void Subject::Notify() {
  change_manager_->Notify();
}

class ChangeManager {
 public:
  Register(Subject*, Observer*);
  Unregister(Subject* Observer*);
  Notify();
}

void ChangeManager::Notify() {
  // A naive implementation that sends notifications to all observers.
  for (Subject* s : subjects_) {
    for (Observer* o : s->observers) {
      o->Update(s);
    }
  }
}

ChangeManager::Notify can be smarter, e.g. maintaining a DAG such that if an Observer* o observes multiple subjects, then o->Update(s) will only be called once to avoid redundant updates.

The ChangeManager is an instance of the Mediator pattern.

Combining Subject and Observer Interfaces in the Same Class

Note that some languages, e.g. Smalltalk, lack multiple inheritance (MI). In such cases, it makes sense to combine the Observer and Subject interface in the same class.

The lack of MI is not an anomaly. C++ got MI because it is a hybrid (contains primitives and objects) language that couldn’t enforce a single monolithic class hierarchy, unlike Smalltalk.

MI’s most famous drawback is the diamond problem being the most prominent con. For example: D inherits from both C and B, who both inherit from A. Both B and C override A::Foo, but D does not; if d->Foo() is called, which Foo gets called?

A good Occam’s Razor is, “If you don’t need to upcast to all of the base classes, prefer composition to multiple inheritance.”

References

  1. Design Patterns > Behavioral Patterns > 7. Observer. Erich Gamma; Richard Helm; Ralph Johnson; John Vlissides. Oct 21, 1994.
  2. Thinking in C++, 2nd Ed, Vol. 2. Chapter 10: Multiple inheritance. Bruce Eckel; Chuck Allison. www.cs.cmu.edu . 1999. Accessed Sep 29, 2021.
  3. ruby on rails - Observers vs. Callbacks. stackoverflow.com . 2009. Accessed Sep 29, 2021.
  4. base/observer_list_types.h - chromium/src - Git at Google. chromium.googlesource.com . Accessed Sep 29, 2021.
  5. base/observer_list.h - chromium/src - Git at Google. chromium.googlesource.com . Accessed Sep 29, 2021.
  6. Iterator invalidation rules for C++ containers. stackoverflow.com . Accessed Sep 29, 2021.
  7. base/observer_list_threadsafe.h - chromium/src - Git at Google. chromium.googlesource.com . Accessed Sep 29, 2021.
  8. base/scoped_observation.h - chromium/src - Git at Google. chromium.googlesource.com . Accessed Sep 29, 2021.
  9. base/scoped_multi_source_observation.h - chromium/src - Git at Google. chromium.googlesource.com . Accessed Sep 29, 2021.
  10. Observer pattern. en.wikipedia.org . Accessed Sep 29, 2021.