Behavioral Patterns  «Prev  Next»
Lesson 8Observer: consequences
Objective Describe some of the Tradeoffs of implementing the Observer Pattern

Tradeoffs when implementing the Observer Pattern

The prime benefit of the Observer pattern is that objects can vary independent of each other. In particular, you can add or delete different Observer objects as needed. The Observer objects may be of the same or different classes as long as they implement the same Observer interface. The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of its methods. The observer pattern is mainly used to implement distributed event handling systems.

Observer is an Interface implemented by Multiple Classes

This is one of those tricky areas where exact details vary a lot from language to language. In Java, the Observer is almost always an interface implemented by multiple classes. In C++, the Observer is likely to be an abstract class mixed in through multiple inheritance. In Smalltalk, the weak typing allows the Observer interface to be merely an abstract idea rather than something directly implemented in code.

Observer Design Pattern

  1. Model the independent functionality with a subject abstraction
  2. Model the dependent functionality with observer hierarchy
  3. The Subject is coupled only to the Observer base class
  4. Observers register themselves with the Subject
  5. The Subject broadcasts events to all registered Observers
  6. Observers pull the information they need from the Subject
  7. Client configures the number and type of Observers

#include <iostream>
#include <vector>
using namespace std;

class Subject {
    // 1. "independent" functionality
    vector <class Observer * > views; // 3. Coupled only to "interface"
    int value;
  public:
    void attach(Observer *obs) {
        views.push_back(obs);
    }
    void setVal(int val) {
        value = val;
        notify();
    }
    int getVal() {
        return value;
    }
    void notify();
};

class Observer {
    // 2. "dependent" functionality
    Subject *model;
    int denom;
  public:
    Observer(Subject *mod, int div) {
        model = mod;
        denom = div;
        // 4. Observers register themselves with the Subject
        model->attach(this);
    }
    virtual void update() = 0;
  protected:
    Subject *getSubject() {
        return model;
    }
    int getDivisor() {
        return denom;
    }
};

void Subject::notify() {
  // 5. Publisher broadcasts
  for (int i = 0; i < views.size(); i++)
    views[i]->update();
}

class DivObserver: public Observer {
  public:
    DivObserver(Subject *mod, int div): Observer(mod, div){}
    void update() {
        // 6. "Pull" information of interest
        int v = getSubject()->getVal(), d = getDivisor();
        cout << v << " div " << d << " is " << v / d << '\n';
    }
};

class ModObserver: public Observer {
  public:
    ModObserver(Subject *mod, int div): Observer(mod, div){}
    void update() {
        int v = getSubject()->getVal(), d = getDivisor();
        cout  << v << " mod "  << d << " is " << v % d  <<  '\n';
    }
};

int main() {
  Subject subj;
  DivObserver divObs1(&subj, 4); // 7. Client configures the number and
  DivObserver divObs2(&subj, 3); //    type of Observers
  ModObserver modObs3(&subj, 3);
  subj.setVal(14);
}

Potential Pitfalls of implementing the Observer Pattern

You should be aware of a few potential pitfalls when implementing the Observer pattern:
  1. Generally, you need to make sure that the change of state is complete and that the object has a consistent state before notifying observers of the change.
  2. Be clear about whether the order in which Observer objects are notified is or is not predictable.
  3. Do not let an Observer register itself twice with the same Observed object unless you are sure that is what it means to do.