Java observer pattern implementation and Java observer pattern evolution

  • 2020-04-01 02:57:15
  • OfStack

Simple observer pattern implementation


import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class SimpleObserverPattern {

 public static void main(String[] args) {
  SimpleObserverPattern sop = new SimpleObserverPattern();

  List<IObserver> observers = new ArrayList<IObserver> ();
  IObserver observerA = sop.new Observer("ObserverA");
  IObserver observerB = sop.new Observer("ObserverB");
  observers.add(observerA);
  observers.add(observerB);

  IObservable observable = sop.new Observable(observers);
  observable.registerObserver(sop.new Observer("ObserverC"));

  observable.changeState();
  observable.close();
 }

 //Being observed, sometimes called Subject
 interface IObservable {
  void registerObserver(IObserver observer);
  void unregisterObserver(IObserver observer);
  void notifyObservers();
  String getState();
  void changeState();
  void close();
 }

 class Observable implements IObservable {

  private static final String NEW = "New";
  private static final String CHANGED = "Changed";
  private static final String CLOSED = "Closed";

  private String state;
  private List<IObserver> observers;

  public Observable() {
   this(null);
  }

  public Observable(List<IObserver> observers) {
   if(observers == null) {
    observers = new ArrayList<IObserver> ();
   }
    this.observers = Collections.synchronizedList(observers);
    this.state = NEW;
  }
  @Override
  public void registerObserver(IObserver observer) {
   observers.add(observer);
  }
  @Override
  public void unregisterObserver(IObserver observer) {
   observers.remove(observer);
  }
  @Override
  public void notifyObservers() {
   Iterator<IObserver> iter = observers.iterator();
   while(iter.hasNext()) {
    iter.next().update(this);
   }
  }

  @Override
  public String getState() {
   return state;
  }

  @Override
  public void changeState() {
   this.state = CHANGED;
   notifyObservers();
  }

  @Override
  public void close() {
   this.state = CLOSED;
   notifyObservers();
  }
 }

 interface IObserver {
  void update(IObservable observalbe);
 }

 class Observer implements IObserver {

  private String name;

  public Observer(String name) {
   this.name = name;
  }
  @Override
  public void update(IObservable observalbe) {
   System.out.println(
     String.format("%s receive observalbe's change, current observalbe's state is %s",
        name, observalbe.getState()));
  }

 }
}

The above implementation directly takes the observed object as a callback function parameter, which is not elegant and might work in simple scenarios.
But in fact, more often than not, an observed has many different events or states, and each observer may be interested in different events or states, or for information hiding purposes, do not want every observer to have access to all the states inside the Observable.
So I continue to evolve the code to the following version, notice that I'm not thinking very carefully about concurrency here.


import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
public class MultiEventObserverPattern {

 public static void main(String[] args) {
  MultiEventObserverPattern meop = new MultiEventObserverPattern();

  IObservable observable = meop.new Observable();

  IObserver observerA = meop.new Observer("ObserverA");
  IObserver observerB = meop.new Observer("ObserverB");

  //Register events of interest
  observable.registerObserver(observable.getEventA(), observerA);
  observable.registerObserver(observable.getEventB(), observerB);

  //Change the observed state
  observable.changeStateA();
  observable.changeStateB();
 }

 interface IEvent {
  void eventChange();
  String getState();
 }

 class EventA implements IEvent {

  private static final String INITIALIZED = "Initialized";
  private static final String PENDING = "Pending";

  private String state;

  public EventA() {
   this.state = INITIALIZED;
  }
  @Override
  public void eventChange() {
   System.out.println("EventA change");
   this.state = PENDING;
  }

  @Override
  public String toString() {
   return "EventA";
  }
  @Override
  public String getState() {
   return state;
  }

 }

 class EventB implements IEvent {

  private static final String NEW = "New";
  private static final String IDLE = "Idle";

  private String state;

  public EventB() {
   this.state = NEW;
  }
  @Override
  public void eventChange() {
   System.out.println("EventB change");
   this.state = IDLE;
  }

  @Override
  public String toString() {
   return "EventB";
  }
  @Override
  public String getState() {
   return state;
  }
 }

 //The Observable, or Subject, can be called
 interface IObservable {
  void registerObserver(IEvent event, IObserver observer);
  void unregisterObserver(IEvent event, IObserver observer);
  //Notifies the observer that an event has occurred
  void notifyObservers(IEvent event);

  void changeStateA();
  void changeStateB();

  IEvent getEventA();
  IEvent getEventB();
 }

 class Observable implements IObservable {

  private IEvent eventA;
  private IEvent eventB;

  private Hashtable<IEvent, Set<IObserver>> eventObserverMapping;

  public Observable() {
   this(null);
  }

  //Here if some Set< is passed in by evenObserverMapping; IObserver> It's not synchronized, so there's no way
  public Observable(Hashtable<IEvent, Set<IObserver>> eventObserverMapping) {
   if(eventObserverMapping == null) {
    eventObserverMapping = new Hashtable<IEvent, Set<IObserver>> ();
   }
   this.eventObserverMapping = new Hashtable<IEvent, Set<IObserver>> ();

   this.eventA = new EventA();
   this.eventB = new EventB();
  }
  @Override
  public void registerObserver(IEvent event, IObserver observer) {
   Set<IObserver> observers = eventObserverMapping.get(event);
   if(observers == null) {
    observers = Collections.synchronizedSet(new HashSet<IObserver> ());
    observers.add(observer);
    eventObserverMapping.put(event, observers);
   }
   else {
    observers.add(observer);
   }
  }
  @Override
  public void unregisterObserver(IEvent event, IObserver observer) {
   Set<IObserver> observers = eventObserverMapping.get(event);
   if(observers != null) {
    observers.remove(observer);
   }
  }
  @Override
  public void notifyObservers(IEvent event) {
   Set<IObserver> observers = eventObserverMapping.get(event);
   if(observers != null && observers.size() > 0) {
    Iterator<IObserver> iter = observers.iterator();
    while(iter.hasNext()) {
     iter.next().update(event);
    }
   }
  }
  @Override
  public void changeStateA() {
   //Changing state A triggers event A
   eventA.eventChange();
   notifyObservers(eventA);
  }
  @Override
  public void changeStateB() {
   //Changing state B triggers event B
   eventB.eventChange();
   notifyObservers(eventB);
  }
  @Override
  public IEvent getEventA() {
   return eventA;
  }
  @Override
  public IEvent getEventB() {
   return eventB;
  }

 }

 interface IObserver {
  void update(IEvent event);
 }

 class Observer implements IObserver {

  private String name;

  public Observer(String name) {
   this.name = name;
  }
  @Override
  public void update(IEvent event) {
   System.out.println(
     String.format("%s receive %s's change, current observalbe's state is %s",
        name, event, event.getState()));
  }

 }
}

It seems perfect, but it's not perfect. Because events are hard-coded as properties of the observed class. The event types are fixed at compile time, and to add new event types you have to change the IObservable interface and Observable classes, which greatly reduces flexibility.
The observed is coupled to these specific events, so how do we break this constraint?
The answer is to introduce a new component that manages the relationship between the event, the observer, and the observed, and that invokes the observer's callback function when the event occurs. This is also a decoupling, sort of like Spring's IOC container.
As for the implementation, I think Guava EventBus has done a pretty good job, refer to the link I mentioned earlier.

PS: this post is not an advertisement for Guava EventBus, but a step by step promotion of my own ideas, and gradually consistent with the design ideas of Guava EventBus.

Let's move on to an example of how the JDK standard class implements the Observer pattern, and then examine its source code implementation, with only one Observable and one Observer interface to look at.

JDK standard classes implement the observer pattern


import java.util.Observable;
import java.util.Observer;

public class JDKObserverDemo {

 public static void main(String[] args) {
  JDKObserverDemo jod = new JDKObserverDemo();

  //The observed
  MyObservable myObservable = jod.new MyObservable("hello");
  //The observer
  Observer myObserver = jod.new MyObserver();
  //registered
  myObservable.addObserver(myObserver);
  //Changes the observed state, triggering the observer callback function
  myObservable.setValue("will");
 }

 class MyObservable extends Observable {

  private String watchedValue;   //The observed value

  public MyObservable(String watchedValue) {
   this.watchedValue = watchedValue;
  }

  public void setValue(String newValue) {
   if(!watchedValue.equals(newValue)) {
    watchedValue = newValue;

    setChanged();
    notifyObservers(newValue);
   }
  }

  @Override
  public String toString() {
   return "MyObservable";
  }
 }

 class MyObserver implements Observer {
  @Override
  public void update(Observable o, Object arg) {
   System.out.println(o + "'s state changed, argument is: " + arg);
  }
 }
}

Taking a look at the Observer and Observable implementations in the JDK standard library is easy enough to cover.
Here is the listener implementation in Quartz.

QuartzScheduler listener


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

public class QuartzScheduler {

 private ArrayList<SchedulerListener> internalSchedulerListeners = new ArrayList<SchedulerListener>(10);
// private ArrayList<JobListener> interanlJobListeners = new ArrayList<JobListener>();   //  a Observable Multiple sets of listeners can be included 

 public Date scheduleJob(Trigger trigger) {
  if(trigger == null) {
   return null;
  }

  System.out.println("Schedule job, trigger: " + trigger);

  notifySchedulerListenersScheduled(trigger);

  return new Date();
 }

 public void unScheduleJob(Trigger trigger) {
  if(trigger == null) {
   return;
  }

  System.out.println("Unschedule job, trigger: " + trigger);

  notifyShedulerListenerUnScheduled(trigger);
 }

 //Registered SchedulerListener
    public void addInternalSchedulerListener(SchedulerListener schedulerListener) {
        synchronized (internalSchedulerListeners) {
            internalSchedulerListeners.add(schedulerListener);
        }
    }

    //Remove SchedulerListener
    public boolean removeInternalSchedulerListener(SchedulerListener schedulerListener) {
        synchronized (internalSchedulerListeners) {
            return internalSchedulerListeners.remove(schedulerListener);
        }
    }

    public List<SchedulerListener> getInternalSchedulerListeners() {
        synchronized (internalSchedulerListeners) {
            return java.util.Collections.unmodifiableList(new ArrayList<SchedulerListener>(internalSchedulerListeners));
        }
    }

    public void notifySchedulerListenersScheduled(Trigger trigger) {
     for(SchedulerListener listener: getInternalSchedulerListeners()) {
      listener.jobScheduled(trigger);
     }
    }

    public void notifyShedulerListenerUnScheduled(Trigger trigger) {
     for(SchedulerListener listener: getInternalSchedulerListeners()) {
      listener.jobUnScheduled(trigger);
     }
    }
}

SchedulerListener


//Listening interface, callback function, the Client needs to provide callback function implementation when registering listening
public interface SchedulerListener {

 void jobScheduled(Trigger trigger);

 void jobUnScheduled(Trigger trigger);
}

The Trigger


// Trigger
public class Trigger {
 private String triggerKey;
 private String triggerName;

 public Trigger(String triggerKey, String triggerName) {
  this.triggerKey = triggerKey;
  this.triggerName = triggerName;
 }

 public String getTriggerKey() {
  return triggerKey;
 }
 public void setTriggerKey(String triggerKey) {
  this.triggerKey = triggerKey;
 }
 public String getTriggerName() {
  return triggerName;
 }
 public void setTriggerName(String triggerName) {
  this.triggerName = triggerName;
 }

 public String toString() {
  return String.format("{triggerKey: %s, triggerName: %s}", triggerKey, triggerName);
 }

}

The Test


public class Test {

 public static void main(String[] args) {
  QuartzScheduler qs = new QuartzScheduler();

  SchedulerListener listenerA = new SchedulerListener() {

   @Override
   public void jobUnScheduled(Trigger trigger) {
    System.out.println("listenerA job unscheduled: " + trigger.getTriggerName());
   }

   @Override
   public void jobScheduled(Trigger trigger) {
    System.out.println("listenerA job scheduled: " + trigger.getTriggerName());
   }
  };
  SchedulerListener listenerB = new SchedulerListener() {

   @Override
   public void jobUnScheduled(Trigger trigger) {
    System.out.println("listenerB job unscheduled: " + trigger.getTriggerName());
   }

   @Override
   public void jobScheduled(Trigger trigger) {
    System.out.println("listenerB job scheduled: " + trigger.getTriggerName());
   }
  };

  //Register the Scheduler Listener
  qs.addInternalSchedulerListener(listenerA);
  qs.addInternalSchedulerListener(listenerB);

  Trigger triggerA = new Trigger("Key1", "triggerA");
  Trigger triggerB = new Trigger("Key2", "triggerB");
  qs.scheduleJob(triggerA);
  qs.scheduleJob(triggerB);
  qs.unScheduleJob(triggerA);
 }
}


Related articles: