-
Notifications
You must be signed in to change notification settings - Fork 3
Observer
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.
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
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.