15 Puzzle - GUI

Let's now consider refactoring our current implementation to support a graphical user interface. This is a very common exercise in software development. While the core functionality of an application may remain fairly stable, how it is accessed by users is routinely quite fluid. Users may want to use the application: on a dumb terminal, on a GUI-capable workstation, on a PDA, in a Web browser, etc.

Another common scenario is an application that would like to present many different look-and-feels. Some users may want a tool bar and keyboard accelerators; while other users may prefer a menu bar and pop-up menus. Along a different dimension, some users may prefer to browse complex data in tables; while other users may prefer graphs and bar charts.

These kinds of "separation of concerns" are so common in software that several reusable architectural approaches have been documented and practiced over the years. The Smalltalk community instituted MVC - Model, View, Controller. Encapsulate the independent functionality in a common core - the Model. Segregate the dependent functionality into as many Views as the requirements call for. Then choreograph all interaction between the two camps by interposing a Controller.

Another manifestation of this same paradigm is the Observer design pattern [footnote]. It prescribes that the sentience, or intelligence, of the application is partitioned in a component called the Subject. Then all optional, and often user-configurable, functionality is captured in as many Observers as there are flavors to the user experience.

The project

Refactor the previous chapter's implementation into a UI-independent model and a DOS view. Create a second interchangeable GUI view.

Interface and Implementation

Whenever two parties are involved in anything (whether its a business, a marriage, or a computer application), a contract or an "understanding" is involved. The reasons for complicating life by bringing together and choreographing the energies and expectations of two parties might be:

For any of these reasons to succeed, the rights and responsibilities of each party must be defined. This enumeration might go by any of several names: protocol, API, interface definition document, design by contract. Whatever name it goes by, a layer of indirection is created that functions as a buffer or membrane between the two parties, and allows: Software developers are careful to distinguish between "interface" and "implementation". The former is the abstract contract that provides all the benefits just discussed. The latter is one (of possibly many) concrete realizations of that abstraction. Using sort functionality as an example; the name of the function, the number and type of all function parameters, and the return type of the function compose the sort interface. The sort implementation could then use any algorithm available in the literature: bubble, shell, merge, heap, etc.

Object-oriented developers are even more jealous about distinguishing between interface and implementation. UML defines an element called "interface". Java defines a construct called "interface". The motivation of each is to define a contract between two or more parties that carefully structures the separation of concerns that needs to be enumerated and enforced.

In our context the parties are: the model, and one or more views. How should development proceed? Should the interface, the model, and one or more views be specified sequentially or simultaneously? If sequentially, which should be developed first?

It is a common practice to design and implement the model, the interface, and one view simultaneously. Then when the second and third views don't mesh well with the interface, the entire project scrambles to recover. Robert Martin wrote an article that documented just such an experience.

Reuse case study

Martin's company was contracted to write two vocational testing applications. If the first two deliverables were successful, then eleven more testing applications would be awarded. Their architecture called for a framework that would be the focus and repository of all reuse. Given the similarity of the applications, they estimated each application would consist of: 85% reused code from the framework, and 15% application-specific code. They decided that reuse would be maximized if the framework and the first application were developed simultaneously.

When the second application was started, Martin was dismayed to learn that the framework wasn't very reusable. This was because proactive reuse is almost always an attempt to exercise 20/20 foresight - something humans are not well-equipped to practice. Grady Booch has suggested, "a framework does not even begin to reach maturity until it has been applied in at least three or more distinct applications."

Martin and company decided to re-develop the framework concurrently with the development of four client applications. "We did not trust our ability to completely anticipate the needs of the [applications]. In short, we felt that the architecture needed almost immediate verification by being used in working [applications]. We did not want to guess."

The purpose for including this reuse tangent is to suggest that an application's model and view interface need to be developed in parallel with three or more views. We won't invest in that level of discipline. We will focus on just two views: a DOS view, and a GUI view.

Two iterations:

  1. draw board
  2. refactor into model, DOS view, and Swing view

Iteration 1

Implement a FifteenPuzzleSwing class and main() that displays the puzzle in a window. Do not implement any "move" functionality yet. Reuse the width and height keyboard input code from the DOS program. When the user selects a square, display the value of the square to standard out. Instructions and issues:

Iteration 2

Now that we have two user interface implementations, let's refactor everything into: a model. two views, and an interface that will provide decoupling between the independent model and the dependent views.

The Observer design pattern recommends centralizing the state of the application in the Subject (aka Model). Whenever changes occur to that data, the Subject communicates with all Observers (aka Views) present. The designer can choose to have the Subject "push" these changes to each Observer, or, the Observers can each "pull" whatever state they desire from the Subject. The first choice increases the coupling between Subject and Observer because the Subject has to know (or assume) what each Observer wants. The second choice incurs more overhead because the Subject sends a message to all Observer, which in turn send a request back to the Subject.

What state exists in our current application? The current location of each square?

A common compromise in Model-View designs is to replicate some amount of the application's state in each View. If there is no replication at all, then each View must go back to the Model, and request all the application data is it responsible for presenting each and every time it needs to refresh its presentation. This replication could be thought of as "caching" - a common technique for improving performance.

Do you want (or need) to cache any application state in either view? Instructions and issues:

Iteration 1 - implementation

Using JButton components makes life a lot simpler than having to draw everything ourselves. Changing the labels on stationary push buttons should do a good job of simulating sliding squares. In listing 3.1, main() looks just like the top half of main() in listing 2.4. It is interesting that the GridLayout constructor requires height as its first parameter, and width as its second. All the rest of listing 3.1 is standard Swing, and outside the scope of this discussion.

Listing 3.1

Iteration 2 - implementation

It would seem ideal if we could migrate all the common "move" functionality to the FifteenPuzzleModel class. This is the core "business logic" of the application, and it is independent of whatever user interface may be layered on top. This has been achieved in listing 3.2.

The only coupling that needs to exist between the Model and its corresponding View is a setLabel() method. When the Model is told to move(), it works through the algorithm we created in the previous chapter, and when swap() is required, the View is updated at the same time as the local state.

Notice that the FifteenPuzzleModel class is dependent only on the FifteenPuzzleView interface. The Model receives the View object in the constructor, and delegates to it in the swap() method.

Listing 3.2

It would seem ideal if the FifteenPuzzleSwing class could be reduced to the actionPerformed() method. This is generally the case in listing 3.3. All presentation functionality (component layout and exposure recovery) is handled for us by the Swing library. All that remains to be handled by us is user interaction.

The FifteenPuzzleSwing class implements the previously defined FifteenPuzzleView interface. This allows the coupling that exists from the Model to the View to be abstract (or indirect).

When the user selects a JButton component, our actionPerformed() method is invoked. If the user has selected the blank space, then no action is required. Otherwise, the JButton's label is converted to an integer and passed to the Model's move() method. If the Model decides the move request is valid, it will update the View by calling setLabel(), which in turn modifies the label of the affected JButton.

Notice that this design not only has the Model updating the View, but the View initiating action on the Model. For this to work, each must hold a reference to the other. This "chicken and egg" cyclic coupling was resolved by passing the View reference to the Model when the latter is created, and then passing the Model reference to the View in a subsequent message.

Listing 3.3

It would seem ideal if the FifteenPuzzleDos class could be reduced to the display() method and the interaction loop. This is demonstrated in listing 3.4. The only interesting difference between this View and the Swing View is that we are caching the width, height, and square label state here. This allows the performance of the display() method to be improved.

Listing 3.4