Design Patterns: Observer

 
  • Behavioural Pattern
  • The observer pattern is a software design pattern in which an object, named the Subject, maintains a list of its dependents, called Observers, and notifies them automatically of any state changes, usually by calling one of their methods.
  • Used for implementing distributed event handling systems, in event driven software.
  • In those systems, the subject is usually named a stream of events or stream source of events, while the observers are called sinks of events.
  • Most modern programming-languages comprise built-in “event” constructs implementing the observer-pattern components.
  • While not mandatory, most observers implementations would use background threads listening for subject-events and other support mechanisms provided by the kernel (Linux epoll, …).
  • The Observer pattern captures the lion’s share of the Model-View-Controller architecture.

Problems Addressed

  • The Observer pattern addresses the following problems:
    • A one-to-many dependency between objects should be defined without making the objects tightly coupled.
      • In some scenarios, Tightly coupled objects can be hard to implement, and hard to reuse because they refer to and know about many different objects with different interfaces.
      • In other scenarios, tightly coupled objects can be a better option since the compiler will be able to detect errors at compile-time and optimize the code at the CPU instruction level.
    • It should be ensured that when one object changes state, an open-ended/any number of dependent objects are updated automatically.
    • It should be possible that one object can notify an open-ended number of other objects.
    • A large monolithic design does not scale well as new graphing or monitoring requirements are levied.

Solution with Subject & Observers

  • Observer pattern defines Subject and Observer objects.
  • When a subject changes state, all registered observers are notified and updated automatically (and probably asynchronously).
  • Responsibility of Subject:
    • Maintain a list of observers
    • Notify them of state changes by calling their update() operation.
  • Responsibility of Observers:
    • Register/Unregister themselves on a subject (to get notified of state changes)
    • Update their state when notified (synchronize their state with the subject’s state).
  • This makes subject and observers loosely coupled.
  • Subject and observers have no explicit knowledge of each other.
  • Observers can be added and removed independently at run-time.
  • This notification-registration interaction is also known as publish-subscribe.

Coupled Vs Pub-Sub implementations

  • Tightly Coupled Implementation
    • Typically, the observer pattern is implemented so the subject being observed is part of the object for which state changes are being observed.
    • This type of implementation forces both the observers and the subject to be aware of each other and have access to their internal parts, creating possible issues of scalability, speed, message recovery and maintenance, the lack of flexibility in conditional dispersion, and possible hindrance to desired security measures.
  • Pub-Sub Implementations:
    • In some (non-polling) implementations of the publish-subscribe pattern (aka the pub-sub pattern), this is solved by creating a dedicated message queue server (and sometimes an extra message handler object) as an extra stage between the observer and the object being observed, thus decoupling the components.
    • In these cases, the message queue server is accessed by the observers with the observer pattern, subscribing to certain messages knowing only about the expected message (or not, in some cases), while knowing nothing about the message sender itself; the sender also may know nothing about the observers.
    • :x: Other implementations of the publish-subscribe pattern, which achieve a similar effect of notification and communication to interested parties, do not use the observer pattern at all.

Limitations

  • Observer Pattern as described in GoF is very basic and does not address:
    • Removing interest in changes to the observed "subject" (Unregister)
    • Special logic to be done by the observed “subject” before or after notifying the observers.
    • Recording of change notifications sent
    • Guaranteeing that notifications are being received.
  • These concerns are typically handled in message queueing systems of which the observer pattern is only a small part.
  • Publish–subscribe pattern
  • Mediator
  • Singleton

UML Class & Sequence diagrams

Observer Pattern UML Class & Sequence diagrams

  • In the above UML class diagram,
    • Subject class does not update the state of dependent objects directly.
    • Instead, Subject refers to the Observer interface update() for updating state, which makes the Subject independent of how the state of dependent objects is updated.
    • The Observer1 and Observer2 classes implement the Observer interface and synchronize their state with subject’s state.
  • The UML sequence diagram shows the run-time interactions:
    • The Observer1 and Observer2 objects call attach(this) on Subject1 to register themselves.
    • Assuming that the state of Subject1 changes, Subject1 calls notify() on itself.
    • notify() calls update() on the registered Observer1 and Observer2 objects, which request the changed data (getState()) from Subject1 to update (synchronize) their state.
  • 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/topic of interest" from the Subject.
  • The push model compromises reuse, while the pull model is less efficient.

Reference Implementations

Observer Pattern Example UML

Python Reference Implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Observable:
  def __init__(self):
    self._observers = []

  def register_observer(self, observer):
    self._observers.append(observer)

  def notify_observers(self, *args, **kwargs):
    for obs in self._observers:
      obs.notify(self, *args, **kwargs)

class Observer:
  def __init__(self, observable):
    observable.register_observer(self)

  def notify(self, observable, *args, **kwargs):
    print("Got", args, kwargs, "From", observable)

subject = Observable()
observer = Observer(subject)
subject.notify_observers("test", kw="python")

# prints: Got ('test',) {'kw': 'python'} From <__main__.Observable object at 0x0000019757826FD0>

Without Observer Pattern Implementation Sample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// The number and type of "Observer" objects are hard-wired in the Subject class.
// The user has no ability to add more observers without modifying Subject class.

class DivObserver {
   int  m_div;
public:
   DivObserver( int div ) { m_div = div; }    // parameterized constructor
   
   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; }    // parameterized constructor
   
   void update( int val ) {
      cout << val << " mod " << m_mod << " is "
           << val % m_mod << '\n';
  }
};

class Subject {
   int  m_value;
   DivObserver  m_div_obj;                    // Composite Objects
   ModObserver  m_mod_obj;
public:
   Subject() : m_div_obj(4), m_mod_obj(3) { } // invoking parameterized constructors

   void set_value( int value ) {
      m_value = value;
      notify();
   }

   void notify() {
      m_div_obj.update( m_value );            // calling Hard-wired objects' methods
      m_mod_obj.update( m_value );
  }
};

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

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

Observer Pattern Implemenation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// The Subject class is now decoupled from the number and type of Observer objects.
// The client/user has asked for two DivObserver delegates (each configured differently),
// and one ModObserver delegate.

class Observer {                          // Abstract class
public:
   virtual void update( int value ) = 0;  // Pure Virtual Method
};

class Subject {
   int  m_value;
   vector  m_views;                       // List of Observers
public:
   void attach( Observer* obs ) {
      m_views.push_back( obs );           // Add observers to list
   }
   
   void set_val( int value ) {
      m_value = value;  notify();         // notify change
   }
   
   void notify() {
      for (int i=0; i < m_views.size(); ++i)
         m_views[i]->update( m_value );   // Push notification to each observer
                                          // invoke `update()` of respective concrete sub-class
  }
};

class DivObserver : public Observer {         // Concrete sub-class of Observer
   int  m_div;
public:
   DivObserver( Subject* model, int div ) {   // Parameterized constructor
      model->attach( this );                  // Subject/Observable object as input
      m_div = div;
   }
   
   /* virtual */ void update( int v ) {       // Implement Pure-Virtual method
      cout << v << " div " << m_div << " is "
           << v / m_div << '\n';
}  };

class ModObserver : public Observer {         // sub-class of Observer Abstract class
   int  m_mod;
public:
   ModObserver( Subject* model, int mod ) {   // Parameterized constructor
      model->attach( this );                  // Subject/Observable object as input
      m_mod = mod;
   }
   
   /* virtual */ void update( int v ) {       // Implement Pure-Virtual method
      cout << v << " mod " << m_mod << " is "
           << v % m_mod << '\n';
}  };

int main( void ) {
   Subject      subj;
   DivObserver  divObs1( &subj, 4 );          // multiple observers of same sub-class
   DivObserver  divObs2( &subj, 3 );          // Require no modification to any implementation
   ModObserver  modObs3( &subj, 3 );          // i.e. Loosely-coupled implementation

   subj.set_val( 14 );                        // state change
}

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

[ToDo: Observer vs Mediator vs Command vs Chain of Responsibility]

References: