COMP 310
Fall 2015

Generic Dispatchers and Observers

Home  Info  Owlspace   Java Resources  Eclipse Resources  Piazza

UNDER CONSTRUCTION -- CHECK BACK OFTEN FOR UPDATES!

We would really like to have a little more type safety in our dispatchers since we are now sending a myriad of different messages through them.  That is, we would like the dispatcher to be typed to match the message types that its observers are able to process.  Otherwise, one could send a message to the observers that they would not understand.

It is important to understand that changing to generic dispatchers and observers has far-reaching implications and will necessitate code changes throughout the BallWorld system.

For a discussion on the issues surrounding the generic typing of dispatchers (Observables) and Observers with respect to eachother, please see the Java Resources page on Covariant and Contravariant Generic Relationships in OO Systems.

This means that at the highest level, our dispatcher interface would look like this:

package util;

/**
 * An dispatcher of messages of type TDispMsg to its registered IObserver<TDispMsg> objects.  
 * The dispatcher is an Observable in the Observer-Observable design pattern
 * though with the difference of always immediately dispatching to the 
 * observers when a message is received.
 * 
 * @author Stephen Wong 
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of messages being dispatched
 */
public interface IDispatcher<TDispMsg> {

	/**
	 * Dispatch the given message to all the registered Observers
	 * @param msg  The message to pass to all the observers
	 */
	public void dispatch(TDispMsg msg);

	/**
	 * Register the given observer in the dispatcher
	 * @param obs  The observer to register
	 */
	public void addObserver(IObserver<TDispMsg> obs);

	/**
	 * Deregister the given observer from this dispatcher. 
	 * @param obs  The observer to deregister
	 */
	public void deleteObserver(IObserver<TDispMsg> obs);
	
	/**
	 * Deregister all observers from this dispatcher.
	 */
	public void deleteObservers();
}

Note:  The name of the dispatching method above has been changed from notifyAll() to dispatch() to avoid conflicts with the inherited Object.notifyAll() which is used for thread management.

The observer is thus similarly defined:

package util;

/**
 * An observer for IDispatcher<TDispMsg>.  When registered with a dispatcher,
 * an observer will receive the TDispMsg-type message that the dispatcher is sends 
 * to its registered observers.
 * 
 * @author Stephen Wong 
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of message that this observer can process
 */
public interface IObserver<TDispMsg> {

	/**
	 * The method that the dispatcher will call
	 * to process the supplied message.
	 * @param dispatcher The dispatcher sending the message
	 * @param msg The message for this observer to process.
	 */
	public void update(IDispatcher<TDispMsg> dispatcher, TDispMsg msg);
	
}

Since our observers are Ball objects that understand IBallCmd messages, the Ball class must implement IObserver<IBallCmd>:

public class Ball implements IObserver<IBallCmd> {
	...
}

 

IBallCmds must also be typed to the dispatcher that is sending them to the Balls, which necessarily is a dispatcher that sends IBallCmds:

package ballworld.model;

import util.IDispatcher;

/**
 * Interface that represents commands sent through the dispatcher to process the balls
 * 
 */
@FunctionalInterface
public abstract interface IBallCmd {
  /**
	 * The method run by the ball's update method which is called when the ball is updated by the dispatcher.
	 * @param context The ball that is calling this method.   The context under which the command is to be run.
	 */
	public abstract void apply(Ball context, IDispatcher<IBallCmd> dispatcher);
}

Note: By annotating IBallCmd as a FunctionalInterface, this enables us to have full compiler support when implementing IBallCmds using lambda expresssions.

Generally, in the BallWorld system, TDispMsg will be set to be IBallCmd since that is the type of message that the Ball objects are capable of processing.

 

Our dispatcher-observer system is now fully type-safe:

// In the model:

private IDispatcher<IBallCmd> _dispatcher = new SetDispatcherSequential<IBallCmd>(); // or any other implementation

...
// code where the dispatcher is invoked...
			_dispatcher.dispatch(new IBallCmd() {

				@Override
				/**
				 * Tells the given ball to updateState, move and bounce.
				 */
				public void apply(Ball context, IDispatcher<IBallCmd> disp) {
					context.updateState(disp);
					context.move();
					context.bounce();
				}
				
			});
			
			// Or in terms of lambda expressions:
			_dispatcher.dispatch((context, disp) -> {
					context.updateState(disp);
					context.move();
					context.bounce();
				});		
				
// -----------------------

// In the ball:			

	public void update(IDispatcher<IBallCmd> o, IBallCmd cmd) {
		 cmd.apply(this, o);
	}
				

 

And since the IUpdateStrategies are capable of sending their own messages out the dispatcher, the IUpdateStrategies must be typed to the kind of messages that the supplied IDispatcher can send out:

package ballworld.model;

import util.IDispatcher;

/**
 * The strategy that runs when a Ball updates its state.
 * 
 * @author Stephen Wong
 * 
 * @param TDispMsg The type of message that the supplied IDispatcher can send. 
 */
public interface IUpdateStrategy<TDispMsg> {

	/**
	 * Initializes the strategy.   Should be called every time the Ball sets a new strategy.
	 * @param context  The ball using this strategy.
	 */
	public void init(Ball context);
	
	/**
	 * Update the state of the context Ball.
	 * @param context  The context (host) Ball whose state is to be updated
	 * @param dispatcher  The Dispatcher who sent the command that is calling through to here.
	 */
	public void updateState(Ball context, IDispatcher<TDispMsg> dispatcher);
	
	/**
	 * A factory for a typed null strategy object.
* Usage: instantiate this factory class using the desired TDispMsg type and then call its make() method * to create the correctly typed null strategy object. */ public static final class NullFactory<TDispMsg> implements IUpdateStrategyFac<TDispMsg> { /** * Returns a no-op null strategy */ @Override public IUpdateStrategy<TDispMsg> make() { return new IUpdateStrategy<TDispMsg>() { @Override /** * No-op * @param context Ignored */ public void init(Ball context) { } @Override /** * No-op * @param context Ignored * @param dispatcher Ignored */ public void updateState(Ball context, IDispatcher<TDispMsg> dispatcher) { } }; } } }

Notes:

 

IMPORTANT:   The above changes are NOT the only changes that are required in the BallWorld system to change over to a generic dispatcher and observers!  Most of the model code will require changes which for the most part will be relatively minor additions of generic type specifications.   The controller code will thus also be affected.   The view code, being decoupled from the model, should not be affected!  

When adding in generic type specifications, always try to type your classes and methods to be as minimally restricted as possible.   If the class does not explicitly depend on the message type being an IBallCmd, then leave the class with an unspecified generic type, i.e. TDispMsg.    For instance, IUpdateStrategies such as StraightStrategy, CurveStrategy, SwitcherStrategy and MultiStrategy do not care what kind of message the dispatcher sends, so they should be left with unspecified TDispMsg generic typing.

 

Example IDispatcher Implementation

The following is an example of an implementation hieirarchy for IDispatcher that uses a thread-safe Set for internal storage of the IObservers.   Two concrete implementations are shown, one that dispatches to the observers sequentially and one that dispatches in parallel:

package util;

import java.util.Collection;


/**
 * An abstract Collection-based IDispatcher.
 * 
 * @author Stephen Wong 
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of message sent to the registered IObservers
 */
public abstract class ACollectionDispatcher<TDispMsg> implements IDispatcher<TDispMsg> {

	/**
	 * Stores the observers.  
	 */
	private final Collection<IObserver<TDispMsg>> observers;  
	
	/**
	 * Constructor for the class.   The Collection that is used needs to be supplied, 
	 * generally by the implementing subclass.   This allows for different types of 
	 * Collections to be used for different purposes.  It is highly recommended that the 
	 * supplied Collection be completely thread-safe to enable the use of 
	 * multiple dispatching threads.
	 * @param observers  The Collection of IObserver<TDispMsg> to use.
	 */
	public ACollectionDispatcher(Collection<IObserver<TDispMsg>> observers) {
		this.observers = observers;
	}
	
	/**
	 * Accessor method for the internal Collection for use by implementing subclasses.
	 * @return The internal Collection of IObservers<TDispMsg>
	 */
	protected Collection<IObserver<TDispMsg>> getCollection() {
		return observers;
	}
	
	/**
	 * {@inheritDoc}<br/>
	 * Implementation: Add the given observer to the internal Collection.
	 */
	@Override
	public void addObserver(IObserver<TDispMsg> obs) {
		observers.add(obs);	
	}

	/**
	 * {@inheritDoc}<br/>
	 * Implementation: Delete the given observer from the internal Collection.
	 */
	@Override
	public void deleteObserver(IObserver<TDispMsg> obs) {
		observers.remove(obs);
	}

	/**
	 * {@inheritDoc}<br/>
	 * Implementation: Delete all the observers from the internal Collection.
	 */
	@Override
	public void deleteObservers() {
		observers.clear();
	}
}

Note:  The Javadoc directive, {inheritDoc}, will insert the documentation from the overridden superclass method at that location.  The <br/> is added to put the statement(s) about the specific implementation on the next line of the documenation and thus visually separate them from the inherited superclass documentation of the method.

 

package util;

import java.util.concurrent.CopyOnWriteArraySet;

/**
 * A Collection-based Dispatcher that uses a CopyOnWriteArraySet.
 * 
 * @author Stephen Wong
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of message sent to the registered IObservers
 */
public abstract class ASetDispatcher<TDispMsg> extends ACollectionDispatcher<TDispMsg> {
	
	/**
	 * The constructor for the class that supplies a CopyOnWriteArraySet instance to the superclass constructor.
	 */
	public ASetDispatcher() {
		super(new CopyOnWriteArraySet<>()); // Type of CopyOnWriteArraySet is inferred by compiler
	}

}

 

package util;

/**
 * A CopyOnWriteArraySet-based IDispatcher that dispatches to its IObservers sequentially.
 * 
 * @author Stephen Wong
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of message sent to the registered IObservers
 */
public class SetDispatcherSequential<TDispMsg> extends ASetDispatcher<TDispMsg> {

	/**
	 * {@inheritDoc}<br/>
	 * Implementation: Sequential iteration through the collection of IObservers.
	 */
	@Override
	public void dispatch(TDispMsg msg) {
		getCollection().forEach(o -> {
			o.update(this, msg);
		});
	}

}

 

package util;

/**
 * A CopyOnWriteArraySet-based IDispatcher that dispatches to its IObservers in parallel.
 * 
 * @author Stephen Wong
 * @author Derek Peirce
 *
 * @param <TDispMsg> The type of message sent to the registered IObservers
 */
public class SetDispatcherParallel<TDispMsg> extends ASetDispatcher<TDispMsg> {

	/**
	 * {@inheritDoc}<br/>
	 * Implementation: Attempts to perform parallel dispatching of the message to the collection of IObservers.  
	 * Note that parallel execution is not guaranteed.
	 */
	@Override
	public void dispatch(TDispMsg msg) {
		getCollection().parallelStream().forEach(o -> {
			o.update(this, msg);
		});
	}
}

The parallel-dispatching dispatcher has the potential to cause markedly different and/or incorrect and/or deadlocking behavior in the BallWorld system.   Try it and see what happens -- if things change, try to figure out why!

 

 


© 2015 by Stephen Wong