A Cleaner MVC Inspired by Continuation-Passing Style
5. Continuation-Passing Style
We earlier said that we can think of this design as asynchronous message passing: each interaction is represented by a message (the invocation of a callback method) which carries an object (AvailabilityNotifier and DownloadConfirmation) that represents the state at a certain point of the interaction. This design presents some similarities with a technique called Continuation-passing style (CPS) . While in direct functional style a subroutine just returns a value, with CPS the caller passes an object, called "continuation", to the subroutine and that object will receive the result of the subroutine and decide how to proceed. In our case, the callbacks AvailabilityNotifier and DownloadConfirmation are doing a very similar job to continuations. I say "similar" since CPS operates with stricter rules and with deeper implications; anyway it is probably correct to say that some ideas in the design described in this article have been inspired by CPS.
The use of continuations in a user interface has been first described in the paper User Interface Continuations (Dennis Quan, David Huynh, David R. Karger, Robert Miller), where the authors pointed out how they can be used to efficiently represent the control flow for the interaction between a user and a user interface. It makes sense to quote the abstract, as it describes the design requirements that have been introduced in the example before:
“Dialog boxes that collect parameters for commands often create ephemeral, unnatural interruptions of a program’s normal execution flow, encouraging the user to complete the dialog box as quickly as possible in order for the program to process that command. In this paper we examine the idea of turning the act of collecting parameters from a user into a first class object called a user interface continuation. Programs can create user interface continuations by specifying what information is to be collected from the user and supplying a callback (i.e., a continuation) to be notified with the collected information. ... Furthermore, user interface continuations, like other continuation-passing paradigms, can be used to allow program execution to continue uninterrupted while the user determines a command’s parameters at his or her leisure.”
6. Between the View and the Controller
In our previous example we have used continuations between a service and a view controller. Let's look at another example of the blueBill News API, this time between the controller and the view. First, let's recall the methods exposed by the view:
public interface LockableView
{
public void lock (@Nonnull UserNotification notification);
public void unlock();
}
public interface NewsView extends LockableView
{
public void bindActions (@Nonnull Action markAllMessagesAsReadAction);
public void populate (@Nonnull PresentationModel newsFeedPM);
public void notifyFeedUnavailable (@Nonnull UserNotificationWithFeedback notification);
public void notifyFeedIsCached (@Nonnull UserNotification notification);
public void notifyAllMessagesMarkedAsRead (@Nonnull UserNotification notification);
public void confirmToDownloadNews (@Nonnull UserNotificationWithFeedback notification);
}
The view has been designed so it exposes methods representing all the possible interactions with the controller. They are typically:
-
requests for rendering data (populate());
-
requests for rendering a user notification (e.g. notifyFeedUnavailable());
-
requests for a feedback from the user (e.g. confirmToDownloadNews()).
There's nothing more, as views are dumb objects: in fact all the logic must stay within the controller. A view implementation is just the binding with the proper UI technology (Swing, Android, etc...) and it's up to this implementation to respect threading constraints. For instance, we can suppose that a Swing implementation will wrap all methods within a EventQueue.invokeLater(). I'll give more details on this in another article.
Now let's focus on the confirmToDownloadNews(), which asks for a confirmation to the user. Again, this method is asynchronous and it has a callback argument that represents the status at this point of the control flow: a UserNotificationWithFeedback, an object which contains something to render on the display (such as the question "Do you want to connect?", etc...) and exposes the methods confirm() and cancel() representing the user response. The view responsibility is to bind this continuation to a dialog box with "Ok" / "Cancel" buttons, so the user response is appropriately conveyed to the NewsViewController. The details of the implementation of UserNotificationWithFeedback will be given in a future article, but we can just recap the exposed operations:
![]() |
Figure 3. UserNotificationWithFeedback
On these premises, the implementation of the interaction by means of the NewsViewController in its method showNewsFeed() is easy and the code is very readable (the funny single underscore _ is just a shortcut for NewsViewController.class, to be passed to a Java resource bundle handler without polluting readability):
private final NewsService.AvailabilityNotifier availabilityNotifier = new NewsService.AvailabilityNotifier()
{
public void notifyFeedAvailable (final @Nonnull RssFeed newsFeed)
{
populateAndUnlockView(newsFeed);
}
public void notifyCachedFeedAvailable (final @Nonnull RssFeed newsFeed)
{
populateAndUnlockView(newsFeed);
view.notifyFeedIsCached(notification().withText(_, "obsoleteNews"));
}
public void notifyFeedUnavailable()
{
markAllMessagesAsReadAction.setEnabled(false);
view.unlock();
view.notifyFeedUnavailable(notificationWithFeedback().withCaption(_, "unavailableNewsTitle")
.withText(_, "unavailableNewsMessage")
.withFeedback(new Feedback()
{
@Override
public void onConfirm()
{
flowController.finish();
}
}));
}
public void notifyFeedCouldBeDownloaded (final @Nonnull NewsService.DownloadConfirmation confirmation)
{
view.confirmToDownloadNews(notificationWithFeedback().withCaption(_, "confirmDownloadTitle")
.withText(_, "confirmDownloadMessage")
.withFeedback(new Feedback()
{
@Override
public void onConfirm()
{
confirmation.confirmDownload();
}
@Override
public void onCancel()
{
view.unlock();
flowController.finish();
}
}));
}
};
@Nonnull
public void showNewsFeed()
{
view.lock(notification().withText(_, "preparingNews"));
newsService.get().getNewsFeed(availabilityNotifier);
}
| Attachment | Size |
|---|---|
| it.tidalwave.bluebill.mobile.news_.png | 32.91 KB |
| it.tidalwave.bluebill.mobile.news_.ui_.png | 13.07 KB |
| package.png | 12.46 KB |
| UserNotificationWithFeedback.png | 21.21 KB |
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)






