Skip to content
kgleong edited this page Oct 28, 2015 · 1 revision

Motivation

The Observer design pattern is also known as the publisher-subscriber model.

When objects need to notify other objects of changes in state, using the Observer pattern has the following benefits:

  • Loose coupling or complete decoupling of objects and their observers.
  • Observers to a single subject object can be added dynamically.
  • An object can broadcast messages and state changes to any number of observers.

Code

public class Subject {
    String mStatus = "initialized";

    Set<Observer> mObserverList = new HashSet<>();

    public void subscribe(Observer observer) {
        mObserverList.add(observer);
    }

    public void unsubscribe(Observer observer) {
        mObserverList.remove(observer);
    }

    public void updateStatus(String status) {
        mStatus = status;
        notify();
    }

    private void notify() {
        for(Observer observer : mObserverList) {
            observer.update(mStatus);
        }
    }
}

The Subject or publisher above has knowledge of its observers. It does not, however, need to know how many observers.

Also, observers can register themselves as needed.

In the example above, whenever a status update occurs on the Subject, it triggers an update() call on all registered Observer instances.

public interface Observer {
    void update(String status);
}

public class ConcreteObserver implements Observer {
    String mName;

    public ConcreteObserver(String name) {
        mName = name;
    }

    public void update(String status) {
        System.out.println("Updating ConcreteObserver " + mName);
        System.out.println("In response to subject status: " + status);
    }
}

The update() method takes the status as an argument, but it could also take the subscribed Subject object if Observer subclasses require different data.

It is important, however, when requesting data from the Subject object that the state is consistent with the time that the notification occurred.

It's very possible that between the time of the notification and when the requested date is returned, the state of the Subject may have changed.

// Client usage:
Observer observerA = new ConcreteObserver("A");
Observer observerB = new ConcreteObserver("B");

Subject subject = new Subject();
subject.subscribe(observerA);
subject.subscribe(observerB);

subject.updateStatus("pending");

// Output:
// Updating ConcreteObserver A
// In response to subject status: pending
// Updating ConcreteObserver B
// In response to subject status: pending

Observer pattern using a Change Manager

When the number of mappings between subjects and observers is large, a manager can be used as a centralized dispatcher.

public class ManagedSubject {
    ChangeManager mChangeManager;
    String mStatus = "initialized";

    public ManagedSubject(ChangeManager changeManager) {
        mChangeManager = changeManager;
    }

    @Override
    public void updateStatus(String status) {
        mStatus = status;
        mChangeManager.notify();
    }
}

The ManagedSubject instances no longer need to manage their subscribers. This is all done by the ChangeManager.

When the status of a ManagedSubject is updated, the ChangeManager is responsible for notifying all subscribed Observer instances.

public class ChangeManager {
    Map<Subject, Set<Observer>> mSubjectToObserverSetMap = new HashMap<>();

    public register(Subject subject, Observer observer) {
        Set<Observer> observerSet = mSubjectToObserverSetMap.get(subject);

        if(observerSet == null) {
            observerSet = new HashSet<>();
            mSubjectToObserverSetMap.put(subject, observerSet);
        }

        observerSet.add(observer);
    }

    public unregister(Subject subject, Observer observer) {
        Set<Observer> observerSet = mSubjectToObserverSetMap.get(subject);

        if(observerSet != null) {
            observerSet.remove(observer);
        }
    }

    public void notify() {
        // Naive broadcast.  Notify all observers.
        for(Map.Entry<Subject, List<Observer> entry : mSubjectToObserverSetMap.entrySet()) {
            Subject subject = entry.getKey();

            for(Observer observer : entry.getValue()) {

                // Pass subject as a parameter
                // so the observer can query it for its
                // state.
                observer.update(subject);
            }
        }
    }
}

The ChangeManager follows the Mediator pattern. Acting as an intermediary, it decouples Observer instances from Subject instances.

Subject to Observer mappings are registered with the ChangeManager.

In the sample code above, the notify() method uses a naive broadcast. It sends a notification to all known mappings.

The Subject is included as a parameter in the observer.update() method so that each Observer can request information from the Subject to determine whether an update is warranted.

The notify() method can also be implemented in a more efficient manner. For example, it might mark Observer instances for update first, then update only marked Observer objects.

Rather than keeping a map of Subject to Observer objects, channels can be used instead. For example, a channel can be a unique String that Observer instances can subscribe to.

Subject objects can then post data to a specific channel. This removes the need for the ChangeManager to be coupled to Subject objects at all.

References

Clone this wiki locally