![]() |
|||
![]() ![]() |
![]() |
|
![]() |
Combined View and Control in JFCThe JFC has taken some liberties in its implementation of the MVC architecture. In the JFC, the visual components double as both views and controllers. The justification for this design is that the visual components interact with the user, and it is burdensome to force that component to send a message to a controller that forwards the control message to the model. This simplification of the architecture leads to more efficient components, but does limit flexibility. The JFC defines a read-only view of the data by defining interfaces for each type of data model. Default implementations for each model interface are also provided. This allows a view to display the contents of any data source that implements the model interface. Defining the data model as an interface allows the JFC to be more efficient than conventional component toolkits. In a conventional toolkit, the data is copied into the component for display. This requires multiple copies of the same data to be present in the application. For example, to populate an AWT List component, code similar to the following can be used. String[] fruits = { "Apple", "Banana", "Orange", "Pear" }; List list = new List(); for( int i = 0; i < fruits.length; i++ list.add( fruits(i)); As seen in this example, the application stores the data to be presented in the List component in an array. The List component itself creates additional storage for the data it displays. Thus there are two copies of the data structure, one in the application and another in the List component. If the applications data array changes, the List will not reflect the change unless the application explicitly updates it with the new data. This forces the application not only to manage the data, but also to keep all views of the data current. This example also suffers the inefficiency of the time taken to add each item to the list. The add method of the List class is synchronized, which, at the time of this writing, is still a costly operation in Java. When using the JFC, your class containing the data could implement the ListModel interface. The ListModel interface provides methods for the view to query the size of the model and retrieve items from the model. The previous example for a List visual component would look like the following in the JFC: class TestListModel extends AbstractListModel { String[] fruits = { "Apple", "Banana", "Orange", "Pear" }; public int getSize() { return( fruits.length ); } public Object getElementAt(int index) { return( fruits[ index ] ); } } // Some view needing to display the fruits list. ListModel model = new TestListModel(); JList list = new JList( model ); This example takes advantage of one of the default model implementations: the AbstractListModel class. Methods to add and remove subscription listeners are inherited from the superclass. The concrete model class only needs to define the methods to return the actual data. The model interfaces, as well as their associated abstract and default implementations, will be presented later in this book as each component is presented. For such a trivial example, creating a class to contain the data may seem like overkill, and probably is. When the data consists of thousands of items, however, the JFC architecture shines. The data source can implement multiple model interfaces, allowing many view components to present the data with various visual representations. Finally, multiple visual components can be developed to read the same model. This allows the data to implement fewer model interfaces and still be presented with many different visual representations. When evaluating a toolkit, performance measures such as How long does it take to add ten thousand items to a list? are often considered. These types of measurements for properly written JFC applications are irrelevant. The reason for this is that ten thousand items are never added to a list. Instead, the data source implements the ListModel interface and is immediately available for display. The difference in these two architectures is demonstrated in the following example: package com.foley.test; import java.awt.*; import javax.swing.*; /** * A simple example to time adding items to an AWT List and * JFC JList components. * * @author Mike Foley **/ public class TestList { /** * Application entry point. * The arguments are not used. * * @param args Command line arguments passed to the application. **/ public static void main( String[] args ) { // // Time adding Strings to an AWT List component. // long d = System.currentTimeMillis(); List awtList = new List(); for( int i = 0; i < 10000; i++ ) { awtList.add( "Item " + i ); } System.out.println( "AWT time: " + ( System.currentTimeMillis() - d ) ); // // Time creating a ListModel and adding it to // a JList component. // d = System.currentTimeMillis(); ListModel model = new AbstractListModel() { public int getSize() { return( 10000 ); } public Object getElementAt( int index ) { return( "Item " + index ); } }; JList jfcList = new JList( model ); System.out.println( "JFC time: " + (System.currentTimeMillis() - d ) ); } } When running this example on a 300MHz Pentium II processor computer with 128MB of RAM, the AWT List creation averaged a whopping 1.65 seconds while the JFC JList creation averaged approximately 0.015 seconds. The JList was two orders of magnitude faster. This obviously is a jaded example because the objects for the JList are lazily created when requested by the view. However, it approximates many real-world programming examples when the data to be displayed is already in a data structure in memory or can be lazily evaluated when, and if, requested. The JFC architecture gives the application developer the flexibility to store and/or evaluate data in the best way for your particular application. It does not force an assumed structure on the data.
|
![]() |
|