Avoid multiple loop in Observer code
I have a class AllListener
to encapsulate multiple Listeners as follows.
The problem is I have to write a loop in each event method(onStart()
, onEnd()
).
It's quite normal way in observer pattern code, but it's bad smell. Any better way to write loop once? Thank you!
class AllListener{
List<Listener> listeners;
void onStart(){
for(Listener l:listeners)//loop
l.onStart();
}
void onEnd(){
for(Listener l:listeners)//loop
开发者_如何学JAVA l.onEnd();
}
}
Avoiding this is hard, as Java still has no closures. Basically you have these choices:
- to use a wrapper class for your "actions"
- to to use reflection (which I would consider way too complicated here)
- to use a library (e.g. functionaljava)
- to generate code using Java's annotation processor [credits to: Little Bobby Tables]
.
class AllListener{
List<Listener> listeners;
private interface Wrapper {
public void run(Listener l);
}
void onStart(){
loop(new Wrapper(){
public void run(Listener l) {
l.onStart();
});
}
void onEnd(){
loop(new Wrapper(){
public void run(Listener l) {
l.onEnd();
});
}
private void loop(Wrapper w) {
for(Listener l:listeners)
w.run(l);
}
}
As you can see, that works, but is less readable as the original version and isn't worth the trouble if you have only two calling methods.
You can write a fire method which wraps the loop and call this method from your onStart, onEnd methods as the following.
class AllListener {
void onStart(){
fire("onStart");
}
void onEnd(){
fire("onEnd");
}
// eventName must be same with the event handler method in Listener class.
private void fire(String eventName) {
for(Listener l : listeners) {
// Call event handler method with reflection.
l.getClass().getMethod(eventName).invoke(l);
}
}
}
You can facilitate java's dynamic proxies to solve this issue:
public class MultiListenerExample {
private ArrayList<OnClickListener> onClickListeners = new ArrayList<OnClickListener>(); private OnClickListener dispatcher;
public void performOnClick(View v) {
dispatch().onClick(v);
}
private OnClickListener dispatch() {
if (dispatcher == null) {
dispatcher = createDispatcher();
}
return dispatcher;
}
private OnClickListener createDispatcher() {
ClassLoader loader = OnClickListener.class.getClassLoader();
Class<?>[] interfaces = new Class[] { OnClickListener.class };
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (OnClickListener listener : onClickListeners) {
// safe to call this since we implement the same interface as the object of the original invocation
method.invoke(listener, args);
}
return null;
}
};
return (OnClickListener) Proxy.newProxyInstance(loader, intefaces, handler);
}
}
Every call on the interface returned by dispatch()
will be propagated into the InvocationHandler
object that is implemented in such a way, that it will loop through the container of the listeners, and will perform the original invocation on each item.
The method can be safely invoked, since the original invocation has been made on the exact same interface that we are about to call.
This solution can work as long as your listeners doesn't have a return value.
精彩评论