Tuesday, November 4, 2014

Observer Design Pattern

On a poll result day, Election Commission keeps everyone posted on trends/results !!!

Observer design pattern is a behavioral pattern. It models relationship between a Subject object and set of dependent Observer objects. This is like a publisher-subscriber model. When the subject's state changes, the observers get notified (may also be updated with new value). It models, communication between two dependent objects without tightly coupling them, so that they can be changed and reused independently. This pattern defines one-to-many relationship between subject and observers. This pattern enables to reuse Subject and Observers and design more flexible object oriented system.

Intent ( as put in Gang of Four book)
"Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."
Subject(or Publisher) maintains list of dependent Observers(or Subscribers) and notifies them automatically of the state changes by calling one of their method.

Implementation

Let's briefly cover major components :
Subject: Stores list of observers and provides interface for modifying observers
ConcreteSubject : Implements Subject interface and sends notification to list of observers on state change
Observer: Object that should be notified of change in state
ConcreteObserver: Implements observer interface to get notified of state changes

Class Diagram (from Gang of four book)

Let's take case of Election Governing body of a democratic country. The day on which votes are counted; it keeps posted to all stakeholders like parties, new channels etc of trends and results. So in this case, Election body becomes subject and all stakeholders interested in updates and news bites play role of observers. Below code simulates this:

package observer.pattern;

public interface ElectionCommission {
 public void register(PollObservers observer);
 public void unregister(PollObservers observer);
 public void notifyObservers();
 public void setResult(Result result);
}

package observer.pattern;

import java.util.ArrayList;
import java.util.List;

public class NorthernElectionCommision implements ElectionCommission {
 private List<PollObservers> observers;
 private Result result;

 public NorthernElectionCommision() {
  observers = new ArrayList<>();
 }

 @Override
 public void register(PollObservers newObserver) {
  observers.add(newObserver);
 }

 @Override
 public void unregister(PollObservers deleteObserver) {
  int index = observers.indexOf(deleteObserver);
  observers.remove(index);
  System.out.println(" Observer " + deleteObserver
    + " removed from index " + index);
 }

 @Override
 public void notifyObservers() {
  for (PollObservers observer : observers) {
   observer.update(result);
  }
 }

 @Override
 public void setResult(Result result) {
  this.result = result;
  this.notifyObservers();
 }
}

package observer.pattern;

public interface PollObservers {
 public void update(Result result);
}

package observer.pattern;

public class ChannelX implements PollObservers {

 @Override
 public void update(Result result) {
  System.out.println(" Bingo !!! ");
  System.out.println(" Breaking news by ChannelX");

  if (result.trending) {
   System.out.println("Candidate :" + result.candidateName
     + " ; from " + result.partyName + " is leading by "
     + result.numOfVotes + " votes");
  } else if (result.won) {
   System.out.println("Candidate :" + result.candidateName
     + " ; from " + result.partyName + " won !!! ");
  }
 }

}

package observer.pattern;

public class ChannelY implements PollObservers {

 @Override
 public void update(Result result) {
  System.out.println(" !!! --- !!! ");
  System.out.println(" Breaking news by ChannelY");

  if (result.trending) {
   System.out.println(result.candidateName + " is leading by "
     + result.numOfVotes + " votes");
  } else if (result.won) {
   System.out.println(result.candidateName + "  won !!! ");
  }
 }
}

package observer.pattern;

public class Result {
 String candidateName;
 String partyName;
 boolean trending;
 boolean won;
 int numOfVotes;
 public Result(String candidateName, String partyName, boolean trending,
   boolean won, int numOfVotes) {
  super();
  this.candidateName = candidateName;
  this.partyName = partyName;
  this.trending = trending;
  this.won = won;
  this.numOfVotes = numOfVotes;
 }
 
 //note that - it's not properly encapsulated
}

package observer.pattern;

public class Client {
 public static void main(String[] args) {
  ElectionCommission ec = new NorthernElectionCommision();
  PollObservers o1 = new ChannelX();
  PollObservers o2 = new ChannelY();

  Result r1 = new Result("Ram Thakkar", "Republican", true, false, 12345);
  Result r2 = new Result("Zen", "Democrat", false, true, 21);

  System.out.println(" with observer o1");
  ec.register(o1);
  ec.setResult(r1);
  System.out.println(" ####################");

  System.out.println(" with observer o1 and o2");
  ec.register(o2);
  ec.setResult(r2);
  System.out.println(" ####################");

  System.out.println(" deleted o1 observer");
  ec.unregister(o1);
  ec.setResult(r2);
  System.out.println(" ####################");

 }
}

Output:
 with observer o1
 Bingo !!!
 Breaking news by ChannelX
Candidate :Ram Thakkar ; from Republican is leading by 12345 votes
 ####################
 with observer o1 and o2
 Bingo !!!
 Breaking news by ChannelX
Candidate :Zen ; from Democrat won !!!
 !!! --- !!!
 Breaking news by ChannelY
Zen  won !!!
 ####################
 deleted o1 observer
 Observer observer.pattern.ChannelX@135fbaa4 removed from index 0
 !!! --- !!!
 Breaking news by ChannelY
Zen  won !!!
 ####################
 

 

Notes

  • The Subject doesn't need to know any thing about Observers other than references of observers and the interface which needs to be called to pass the information. We could also store the subject-observers mapping in a map.
  • The Subject might send non-relevant updates to an Observer (especially if there are more than one subjects). To avoid this, the subject can pass some meta data like its own reference so that observers can make decisions. 
  • Make sure that the subject state is consistent before calling notify method. 
  • At one extreme is, push model where subject sends all data to the observers, irrespective of they need it or not. At other extreme is the pull model, subject send a light weight notification to all observers and then observers in return ask for details if they need it. 
  • Architectural design pattern, MVC uses observer pattern. MVC's model class acts as subject and view is like observer. If the data in model changes the corresponding view changes automatically. 

References:
http://www.dcs.bbk.ac.uk/~oded/OODP13/Sessions/Session6/Observer.pdf

No comments:

Post a Comment