This is actually a very good question - and perhaps one without a definitive answer. This is a problem that applies to many frameworks, not just GWT, so I like your idea of looking at this with some simplified code. I will do it a little longer to show how even very simple callbacks look like:
Nested Callbacks
alice.call("a", new Callback() { @Override public void onSuccess() { bob.call("b", new Callback() { @Override public void onSuccess() { charlie.call("c", new Callback() { @Override public void onSuccess() { daisy.call("d", new Callback() { @Override public void onSuccess() {
Named Callbacks
You can use your IDE to reorganize this easily into named callbacks (hint: read the callbacks from the bottom up!):
final Callback daisyCallback = new Callback() { @Override public void onSuccess() {
- Problem: The control flow is not immediately apparent.
- However, the IDE can help by using Link Finder (Ctrl-G in Eclipse) or something similar.
Event Bus (or Observer / Publish-Subscribe Template)
Here's what the same calls look like with the event bus:
alice.call("a", new Callback() { @Override public void onSuccess() { bus.fireEvent(BusEvent.ALICE_SUCCESSFUL_EVENT); } }); bus.addEventListener(BusEvent.ALICE_SUCCESSFUL_EVENT, new BusEventListener() { @Override public void onEvent(final BusEvent busEvent) { bob.call("b", new Callback() { @Override public void onSuccess() { bus.fireEvent(BusEvent.BOB_SUCCESSFUL_EVENT); } }); } }); bus.addEventListener(BusEvent.BOB_SUCCESSFUL_EVENT, new BusEventListener() { @Override public void onEvent(final BusEvent busEvent) { charlie.call("c", new Callback() { @Override public void onSuccess() { bus.fireEvent(BusEvent.CHARLIE_SUCCESSFUL_EVENT); } }); } }); bus.addEventListener(BusEvent.CHARLIE_SUCCESSFUL_EVENT, new BusEventListener() { @Override public void onEvent(final BusEvent busEvent) { daisy.call("d", new Callback() { @Override public void onSuccess() { bus.fireEvent(BusEvent.DAISY_SUCCESSFUL_EVENT); } }); } }); bus.addEventListener(BusEvent.DAISY_SUCCESSFUL_EVENT, new BusEventListener() { @Override public void onEvent(final BusEvent busEvent) {
- Under the right circumstances (when it is very clear what each event means, and if you have too many of them), this template can make things very beautiful and understandable.
- But in other cases, it can make the control flow more confusing (and you can easily get twice the lines of code).
- It’s harder to use your IDE to learn about the control flow.
- The GWT history engine is a very positive example of where it is wise to use this method.
Divide and conquer
In my experience, it is often a good idea to “divide and win” by mixing nested and named callbacks:
final Callback charlieCallback = new Callback() { @Override public void onSuccess() { daisy.call("d", new Callback() { @Override public void onSuccess() {
Depending on the situation, two nested callbacks often remain readable, and they reduce the transitions between methods when reading code by 50%.
(I created a pastebin of my examples here if you like playing with them: http://pastebin.com/yNc9Cqtb ) sub>