Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

A Cleaner MVC Inspired by Continuation-Passing Style

06.22.2011
| 16235 views |
  • submit to reddit

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:

  1. requests for rendering data (populate());

  2. requests for rendering a user notification (e.g. notifyFeedUnavailable());

  3. 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:

UserNotificationWithFeedback

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);
      }
AttachmentSize
it.tidalwave.bluebill.mobile.news_.png32.91 KB
it.tidalwave.bluebill.mobile.news_.ui_.png13.07 KB
package.png12.46 KB
UserNotificationWithFeedback.png21.21 KB
Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)