Observer

Intent

Problem

A large monolithic design does not scale well as new graphing or monitoring requirements are levied.

Structure Summary

Structure category:  
wrapper + inheritance hierarchy
Similar patterns:   Builder   Bridge   State

Discussion

Define an object that is the "keeper" of the data model and/or business logic (the Subject). Delegate all "view" functionality to decoupled and distinct Observer objects. Observers register themselves with the Subject as they are created. Whenever the Subject changes, it broadcasts to all registered Observers that it has changed, and each Observer queries the Subject for that subset of the Subject's state that it is responsible for monitoring.

This allows the number and "type" of "view" objects to be configured dynamically, instead of being statically specified at compile-time.

The protocol described above specifies a "pull" interaction model. Instead of the Subject "pushing" what has changed to all Observers, each Observer is responsible for "pulling" its particular "window of interest" from the Subject. The "push" model compromises reuse, while the "pull" model is less efficient.

Issues that are discussed, but left to the discretion of the designer, include: implementing event compression (only sending a single change broadcast after a series of consecutive changes has occurred), having a single Observer monitoring multiple Subjects, and ensuring that a Subject notify its Observers when it is about to go away.

The Observer pattern captures the lion's share of the Model-View-Controller architecture that has been a part of the Smalltalk community for years.

Structure

Subject represents the core (or independent or common or engine) abstraction. Observer represents the variable (or dependent or optional or user interface) abstraction. The Subject prompts the Observer objects to do their thing. Each Observer can call back to the Subject as needed.

Example

The Observer defines a one-to-many relationship so that when one object changes state, the others are notified and updated automatically. Some auctions demonstrate this pattern. Each bidder possesses a numbered paddle that is used to indicate a bid. The auctioneer starts the bidding, and "observes" when a paddle is raised to accept the bid. The acceptance of the bid changes the bid price which is broadcast to all of the bidders in the form of a new bid. [Michael Duell, "Non-software examples of software design patterns", Object Magazine, Jul 97, p54]

Check list

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

Before and after

BeforeAfter
// The number and type of "user interface" (or
// dependent) objects is hard-wired in the
// Subject class.  The user has no ability to
// affect this configuration.

class DivObserver {
   int  m_div;
public:
   DivObserver( int div ) { m_div = div; }
   void update( int val ) {
      cout << val << " div " << m_div << " is "
           << val / m_div << '\n';
}  };

class ModObserver {
   int  m_mod;
public:
   ModObserver( int mod ) { m_mod = mod; }
   void update( int val ) {
      cout << val << " mod " << m_mod << " is "
           << val % m_mod << '\n';
}  };

class Subject {
   int  m_value;
   DivObserver  m_div_obj;
   ModObserver  m_mod_obj;
public:
   Subject() : m_div_obj(4), m_mod_obj(3) { }
   void set_value( int value ) {
      m_value = value;
      notify();
   }
   void notify() {
      m_div_obj.update( m_value );
      m_mod_obj.update( m_value );
}  };

int main( void ) {
   Subject  subj;
   subj.set_value( 14 );
}

// 14 div 4 is 3
// 14 mod 3 is 2
  
// The Subject class is now decoupled from
// the number and type of Observer objects.
// The client has asked for two DivObserver
// delegates (each configured differently),
// and one ModObserver delegate.

class Observer {
public:
   virtual void update( int value ) = 0;
};

class Subject {
   int  m_value;
   vector  m_views;
public:
   void attach( Observer* obs ) {
      m_views.push_back( obs );
   }
   void set_val( int value ) {
      m_value = value;  notify();
   }
   void notify() {
      for (int i=0; i < m_views.size(); ++i)
         m_views[i]->update( m_value );
}  };

class DivObserver : public Observer {
   int  m_div;
public:
   DivObserver( Subject* model, int div ) {
      model->attach( this );
      m_div = div;
   }
   /* virtual */ void update( int v ) {
      cout << v << " div " << m_div << " is "
           << v / m_div << '\n';
}  };

class ModObserver : public Observer {
   int  m_mod;
public:
   ModObserver( Subject* model, int mod ) {
      model->attach( this );
      m_mod = mod;
   }
   /* virtual */ void update( int v ) {
      cout << v << " mod " << m_mod << " is "
           << v % m_mod << '\n';
}  };

int main( void ) {
   Subject      subj;
   DivObserver  divObs1( &subj, 4 );
   DivObserver  divObs2( &subj, 3 );
   ModObserver  modObs3( &subj, 3 );
   subj.set_val( 14 );
}

// 14 div 4 is 3
// 14 div 3 is 4
// 14 mod 3 is 2

Rules of thumb

Chain of Responsibility, Command, Mediator, and Observer, address how you can decouple senders and receivers, but with different trade-offs. Chain of Responsibility passes a sender request along a chain of potential receivers. Command normally specifies a sender-receiver connection with a subclass. Mediator has senders and receivers reference each other indirectly. Observer defines a very decoupled interface that allows for multiple receivers to be configured at run-time. [GoF, p347]

Mediator and Observer are competing patterns. The difference between them is that Observer distributes communication by introducing "observer" and "subject" objects, whereas a Mediator object encapsulates the communication between other objects. We've found it easier to make reusable Observers and Subjects than to make reusable Mediators. [GoF, p346]

On the other hand, Mediator can leverage Observer for dynamically registering colleagues and communicating with them. [GoF, p282]