JavaFX: Using Patterns & Clean Code
Join the DZone community and get the full member experience.
Join For FreeSo, let's introduce a very simple project - a Contact List whose specifications are:
- We have a list of "contacts", where each item is name, last name, email, phone, etc...
- The UI shows a list of contacts, that can be filtered by a text field; if you enter a value in it, only the contacts whose name starts with the entered text are shown. Selections are applied as you type.
- Selecting an item in the list of contacts, the details are shown in a form, where you can edit them.
- At any selection change, the form is animated (a rotation and a blur effect).
You can get the code from:
svn co -r 37 https://kenai.com/svn/javafxstuff~svn/trunk/ContactList/src/ContactList
The model
Let's first have a quick look at the model classes. First a simple value object representing a piece of data:
package it.tidalwave.javafxstuff.contactlist.model;
public class Contact
{
public var id: String;
public var firstName: String;
public var lastName: String;
public var phone: String;
public var email: String;
public var photo: String;
override function toString()
{
return "\{id: {id}, value: {firstName} {lastName}, phone: {phone}, email: {email}"
}
}
Then a small service which provides a bunch of data:
package it.tidalwave.javafxstuff.contactlist.model;
public abstract class ContactRegistry
{
public abstract function items() : Contact[];
}
In the demo code, you'll find a mock implementation with some wired values; in a real case this could be a Business Delegate encapsulating code for retrieving data remotely.
So far, so good - it's pretty normal to keep these things separated from the UI.
The Controllers
We're not going to see a classic Controller here; actually, we're slightly departing from the "pure" MVC. The code I'm showing you is more a "Presentation Model", a pattern described by Martin Fowler as:
The essence of a Presentation Model is of a fully self-contained class that represents all the data and behavior of the UI window, but without any of the controls used to render that UI on the screen. A view then simply projects the state of the presentation model onto the glass.
This class is basically an hybrid between a classic model and a controller. It is also a façade between the view and the domain model.
package it.tidalwave.javafxstuff.contactlist.model;
import it.tidalwave.javafxstuff.contactlist.model.ContactRegistry;
import it.tidalwave.javafxstuff.contactlist.model.ContactRegistryMock;
public class PresentationModel
{
public var searchText : String;
public var selectedIndex : Integer;
// The Business Delegate
def contactRegistry = ContactRegistryMock{} as ContactRegistry;
def allContacts = bind contactRegistry.items();
// The contacts filtered according the contents of the search field
public-read def contacts = bind allContacts[contact | "{contact.firstName} {contact.lastName}".startsWith(searchText)];
// The selected contact; the code also triggers a notification at each change
public-read def selectedContact = bind contacts[selectedIndex] on replace previousContact
{
onSelectedContactChange();
};
// Notifies a change in the current Contact selection
public-init var onSelectedContactChange = function()
{
}
}
Note the extreme compactness brought by functional programming. There's almost no imperative programming as everything is achieved by properly using the binding feature. The only imperative part is the 'onSelectedContactChange' function, which is just a listener to notify selection changes to some external code - it will be used only for triggering the animation.
BTW, I'd like to remove it from here, but I wasn't able to. Maybe it's a JavaFX thing that I've not understood yet, but I'm keeping it for another post.
Now, everything about the animation goes encapsulated in a specific class, which only exposes two properties controlling the animation: the effect and the rotation angle. A single play() function is provided to start the animation.
package it.tidalwave.javafxstuff.contactlist.view;
import javafx.animation.Interpolator;
import javafx.animation.Timeline;
import javafx.scene.effect.GaussianBlur;
public class AnimationController
{
public-read var rotation = 0;
public-read def effect = GaussianBlur{}
def timeline = Timeline
{
repeatCount: 1
keyFrames:
[
at(0s) { effect.radius => 20; rotation => 45 }
at(300ms) { effect.radius => 0 tween Interpolator.EASEBOTH;
rotation => 0 tween Interpolator.EASEBOTH }
]
}
public function play()
{
timeline.playFromStart();
}
}
The Views
Now, a UI component. The way we design it largely depends on the process, as a graphic designer could be involved. In any case, I think that the whole UI should not be implemented in a single, bloated class; rather relevant pieces should be split apart. For instance, a CustomNode can model the "form" that renders the contact details (in the code below I've omitted all the attributes related to rendering):
package it.tidalwave.javafxstuff.contactlist.view;
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.Node;
import javafx.ext.swing.SwingLabel;
import javafx.ext.swing.SwingTextField;
import it.tidalwave.javafxstuff.contactlist.model.Contact;
public class ContactView extends CustomNode
{
public var contact : Contact;
public override function create() : Node
{
return Group
{
content:
[
VBox
{
content:
[
SwingLabel
{
text: bind "{contact.firstName} {contact.lastName}"
}
HBox
{
content:
[
SwingLabel { text: "First name: " }
SwingTextField { text: bind contact.firstName }
]
}
HBox
{
content:
[
SwingLabel { text: "Last name: " }
SwingTextField { text: bind contact.lastName }
]
}
HBox
{
content:
[
SwingLabel { text: "Email: " }
SwingTextField { text: bind contact.email }
]
}
HBox
{
content:
[
SwingLabel { text: "Phone: " }
SwingTextField { text: bind contact.phone }
]
}
]
}
]
};
}
}
As you can see, we have only layout and data binding here, the only things a view should do.
Putting all together
Now the last piece of code, the Main, which builds up the application (again, I've omitted all attributes only related to rendering):
package it.tidalwave.javafxstuff.contactlist.view;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.ext.swing.SwingLabel;
import javafx.ext.swing.SwingList;
import javafx.ext.swing.SwingListItem;
import javafx.ext.swing.SwingTextField;
import it.tidalwave.javafxstuff.contactlist.controller.PresentationModel;
Stage
{
def animationController = AnimationController{};
def presentationModel = PresentationModel
{
onSelectedContactChange : function()
{
animationController.play();
}
};
scene: Scene
{
content: VBox
{
content:
[
HBox
{
content: SwingLabel { text: "Contact List" }
}
HBox
{
content:
[
SwingLabel { text: "Search: " }
SwingTextField { text: bind presentationModel.searchText with inverse }
]
}
HBox
{
content:
[
SwingList
{
items: bind for (contact in presentationModel.contacts)
{
SwingListItem { text:"{contact.firstName} {contact.lastName}" }
}
selectedIndex: bind presentationModel.selectedIndex with inverse
}
ContactView
{
contact: bind presentationModel.selectedContact
effect: bind animationController.effect
rotate: bind animationController.rotation
}
]
}
]
}
}
}
As in the previous code snippets, I think the listing can be easily read and understood. Basically, we are glueing all the pieces together and binding the relevant models.
In the end, each class in this small project does a simple, cohese thing: representing data, encapsulating the presentation logic, controlling the animation, rendering the views. Dependencies are reduced to the minimum and they have the correct direction: views depend on the models (and not the opposite) and the AnimationController; the AnimationController is independent. You could replace the view components without affecting the rest of the classes, as well as removing or adding other animations by properly using different AnimationControllers. This good separation of roles and responsibilities is the good way to apply OO.
There is a detail which is worth discussing. Note the two bind ... with inverse. They implement the so-called "bidirectional binding" where not only a change in the model (e.g. PresentationModel.selectedIndex) is reflected to attributes in the UI (e.g. SwingList.selectedIndex), but also the opposite happens. Indeed, the reverse binding is more important in our example, because it implements the controller responsibility (it captures user's gestures from the view and changes the model); the direct binding from the PresentationModel to SwingList, instead, is useless, as in our case the PresentationModel is never the originator of a change.
So, why not using a simple, direct binding in PresentationModel towards SwingList? Such as:
public class PresentationModel
{
var list : SwingList;
public def selectedIndex = bind list.selectedIndex;
...
}
Because this would introduce a dependency from the model/controller to the view, which is plain wrong. Here, bind ... with inverse not only works as a shortcut for writing less code (that is, explicitly declaring a binding and its inverse), but it's also an essential feature for a better design.
As far as I know - but I could be wrong - for example in ActionScript (Adobe Flex language) there's no bidirectional binding (you need to put binding keywords at both ends of the association), thus introducing unneeded or circular dependencies. I believe this is true at least at code level (as far as I understand, there are different ways to do binding in ActionScript).
Opinions expressed by DZone contributors are their own.
Comments