Context
- APIs in Java often require listeners to implement a specific interface.
- Listener interfaces are often quite similar, varying generally in the name of the method, and the manner in which context is given.
- Different levels of abstraction may need semantically different but functionally equivalent listener interfaces.
- Many possible events may result very similar reactions.
Problem
- The typical method of defining a specific listener class for each type of listener required can become cumbersome, especially if the same behaviour needs to be implemented through several interfaces, or levels of abstraction.
Forces
- A common listener interface would be beneficial.
- Listener interface names tend to be nondescriptive, showing the 'what', but not the 'why'.
- A common interface needs to be generally useful to show much benefit.
- Tricky non-obvious solutions are hard to follow.
Solution
Define a base class with an abstract no-arg no-return method, an input member variable, an output member variable, and at least one 'invocation' method, which will populate the input, call the abstract method, and return the contents of the output. Use this base class anywhere you would normally use a typed listener, using an adapter as necessary. Call the invocation method to notify the listener, passing in the context if necessary.
[A question--why would you use member variables? Why wouldn't the abstract method just take the input context Object as a parameter and return the resulting output Object? Wouldn't this avoid the clone-on-invocation requirement below?]
Example
class Something{
private Method listener;
public void somethingCausingAnEvent(){
listener.invoke();
}
public void doWhenSomethingHappens(Method listener){
this.listener = listener;
}
}
class ExitProgram extends Method{
protected void method(){
System.exit(0);
}
}
class PrintObject extends Method{
protected void method(){
System.out.println(parameter);
}
}
class Notification extends Method{
public synchronized void doWait(){
try{
wait();
}catch(InterruptedException ignore){
}
}
protected void method(){
notifyAll();
}
}
class Method {
private Object parameter;
private Object returnValue;
public Object invoke(){
invoke(null);
}
public Object invoke(Object parameter){
this.parameter = parameter;
method();
return returnValue;
}
protected abstract void method();
}
Resulting Context
- Listener code becomes reusable.
- Listener classes become named for their effects (i.e., a good name for a method), not for the events that cause them.
- A small set of test and utility Methods can be created, as opposed to creating different ones for every use.
- High level listeners can be passed directly through to the low level events which cause them, without breaking encapsulation.
- A GenericListenerCollection? becomes trivially easy to create, and allows for some neat tricks as well :)
- Not very common, and therefore could become a source of confusion.
- Parameters must be passed in as Object, this can require some casting... then again, I've found it lends a degree of dynamisism that is often lacking from Java.
- Threading issues need to be considered. A naive implementation, although fine for many purposes, is specifically thread-unsafe. Generally, a thread safe version will clone the method on every invocation (similar to creating a new stack frame for a normal method invocation).
- 'addActionListener(new ActionListener(){...});' methods start turning into 'doWhenButtonClicked(new CloseProgram());'... they get treated more like normal methods than blobs of code inside a method.
- Gives a nice little bit of simulated DynamicTyping in a StubbornlyStaticTyped language.
Known Uses
Currently it's making appearances in my personal Collections implementations (forEach() type stuff), listeners, instead of the occasional global variable (pass in a method which will return the current FooBar, even if you don't know which one it'll be yet). Don't think this quite qualifies as three distinct uses though :)
Related Patterns
- Basically, a specialization (of purpose) / generalization (of form) of a MethodObject... blocks for Java.
- GenericListenerCollection? (when I get to writing it... couple of interesting things there :)
--
WilliamUnderwood
CategoryJava