JTF > Rationale > The acm.program Package |
In the taxonomy of problems presented in Chapter 3, one of the issues that rose to the fore (as part of the discussion under problem L1) is that Java applications start by invoking a method whose signature is
|
The structure of the main method in an application has several negative effects. One of the most commonly cited is that the method signature for main includes several conceptsstatic methods and array parameters, for examplethat are difficult to present to novices. This shortcoming, however, is only part of the problem. The Java Task Force views the following problems as being of at least as much concern:
To minimize these negative effects, the Java Task Force offers a new Program class as part of its standard library packages. The central idea behind the proposal is simply that the existence of such a classcoupled with several specific subclasses that define specific program typesgives instructors and students a standard framework for creating better, more functional applications.
We believe that the facilities offered by the acm.program package will have a profoundly simplifying effect on introductory Java examples. The major advantages associated with the use of this package can be summarized as follows:
As is often the case, however, these advantages are much easier to understand if one has some familiarity with the general idea of the proposal. This chapter, therefore, begins by illustrating the Program class with a few simple examples, and then describes in more detail the subclasses available in the standard acm.program hierarchy.
Figure 6-1. Class diagram for the acm.program package
As an example, Figure 6-2 shows a program that descends directly from the Program class and has the effect of reading two integers from the user and displaying their sum. Because this program makes very little use of the object-oriented paradigm, it is by no means representative of the program that the Java Task Force encourages, but will nonetheless serve as a useful baseline for illustrating the operation of the class.
Figure 6-2. Simple program to add two numbers
/* * File: Add2.java * --------------- * This program adds two numbers and prints their sum. Because * this version is a Program, input and output are assigned to * System.in and System.out. */ import acm.program.*; public class Add2 extends Program { public void run() { println("This program adds two numbers."); int n1 = readInt("Enter n1: "); int n2 = readInt("Enter n2: "); int total = n1 + n2; println("The total is " + total + "."); } } |
The Add2 class in Figure 6-2 defines a single method called run, which specifies the code that needs to be executed when the program is run. In this example, the run method reads two integers, adds them together, and displays the result. Because this implementation is a direct subclass of Program, input is read from the standard input stream (System.in) and output appears on the standard output stream (System.out). No windows are created by the Program class itself, which means that the Add2 program might produce the following typescript in a command-line environment, shown here as it appears in a terminal window running on Mac OS X:
The Program subclasses at the bottom of Figure 6-1 make it possible to change the behavior of this program in relatively small but highly useful ways. If the first line of the class definition is changed to
|
then the same program runs in a window that contains an IOConsole as defined in Chapter 4. That program might look like this if it were run under Windows XP:
If the definition of the Add2 class were instead changed to extend DialogProgram, the program would then operate by putting up a series of dialog boxes, as illustrated in Figure 6-3 using the Mac OS X style. (The GraphicsProgram class is not appropriate to the add-two-numbers example and will be discussed in section 6.4.)
Figure 6-3. Dialogs produced by the DialogProgram version of Add2
So far, so good. The trouble for teachers trying to offer new students a compelling justification for object-oriented design comes in trying to reflect the intuitive notion of class hierarchies in a programming context. Textbooks often struggle to find appropriate, programming-related examples. Most end up relying on a graphical hierarchy similar to the one outlined in Chapter 5. That hierarchy provides examples that are similar to the biological model: a GStar (as defined in Figure 5-11) is a subclass of GPolygon, which is in turn a subclass of GObject. The structure of the graphics classes supports the intuition that students derive from real-world hierarchies: all stars are polygons, all polygons are graphical objects, but not all graphical objects are polygons, let alone stars.
While the hierarchy from acm.graphics works well as an example, the Task Force believes that it is possible to illustrate this same idea even earlier by making the intuitive notion of a program into a hierarchy in its own right. Students are then exposed to the notions of objects, classes, and subclass hierarchies as the first concepts they encounter. By establishing a hierarchy of programs, students can see how their particular program is an instance of a specific program category, which is itself a subcategory of a larger, more generic class of programs. In particular, students will quickly come to understand that every ConsoleProgram is also a Program and can perform the operations that a Program does, subject to any extensions at the ConsoleProgram level. This understanding of the fundamental is-a relationship should help to emphasize the idea of inheritance.
The most important thing to understand about the design of the acm.program package is the merging of the applet and application roles. The Program class extends JApplet, and is therefore available for use by a web browser. When invoked as an applet, the browser instantiates an instance of the main applet class and invokes its init method. Each Program class, however, also includes an implementation of a method with the familiar signature
|
which allows it to be invoked as an application. As part of its startup operation, the application version of a Program also calls the init method, which makes it possible to include initialization code in a way that is analogous to the applet mode of operation. The main method in the Program class identifies the class that invoked it, instantiates a new instance of that class, and then calls the init and start methods of that program, just as a browser running an applet would do. Thus, in both the applet and application frameworks, the program runs in the context of an instantiated object rather than in the static domain.
Irrespective of whether the program is invoked as an applet or application, the initialization code establishes a component hierarchy that matches the one used in the Swing JApplet class. Moreover, as in Java 2 Standard Edition 5.0, the add methods for the Program class automatically forward any add requests to the content pane, so that it is now possible to write
|
instead of the novice-unfriendly
|
By default, the Program class itself doesnt add anything to the content pane, although subclasses are free to do so. If the content pane has any components at the end of the initialization phase, the implementation calls setVisible(true) on the program frame to display it on the screen. If the content pane is empty, that call does not occur. Thus, programs that are written to use only the standard input streams or popup dialogs for their user interactions can run without displaying a window.
After setting up the standard JApplet component hierarchy, the Program startup code executes the following three steps:
Although it can be useful to include both methods in a single program, most student programs will define either an init method or a run method, but not both. The init method is used for interactive applications in which the program creates and arranges a set of components and then waits for the user to trigger some action, typically by clicking on a button. The run method is intended for programs that have an independent thread of control unrelated to the actions of the user. Programs that animate a graphical object or require console interaction are usually of this form.
Consider, for example, the Add2 program presented at the beginning of this chapter. That program establishes a dialog with the user through the console that requires sequential operation and therefore requires a run method, which for the Add2 program looks like this:
|
The run method in a Program is analogous to the main method in a Java application. The key difference is that the run method is not static and therefore has an object to work with. It can use instance variables in that object and can invoke other methods that are not themselves static. This change alone represents the central advantage of the acm.program package.
The February 2005 release of the acm.program package did not describe the init method, although it was there as part of the standard applet paradigm. At the time, we proposed that initialization occur in a constructor for the class. That strategy, however, did not prove viable. The constructor for a program is invoked very early and therefore has no access to any contextual information. Inside the constructor, for example, it is impossible to determine the dimensions of the applet frame, to perform any operations that require a code base such as loading an image, or even to determine whether one is running in an application or applet context. Students found these restrictions very difficult to understand. The init method is invoked after the object has been instantiated and installed in the frame, which means that the program has access to the required information.
A list of the public methods in the Program class appears in Figure 6-4. A large fraction of these methods implement the IOModel interface or one of the event interfaces from java.awt.event. To save space in the table, these methods are not described in full. The details of the IOModel methods are described in Chapter 4.
Figure 6-4. Public methods common to all Program objects
|
Every Program object is given both an IOConsole and IODialog object at initialization time. By default, the console is IOConsole.SYSTEM_CONSOLE and the dialog is a default instance of IODialog. Subclasses can substitute specially tailored instances of these classes by overriding the protected factory methods createConsole and createDialog. The ConsoleProgram subclass, for example, overrides createConsole as follows:
|
The Program class also defines two public methodsgetInputModel and getOutputModelto specify the source and destination for the input and output methods, respectively. In the Program class itself, these methods return the result of getConsole. The DialogProgram subclass overrides these methods to return the result of getDialog instead. Indeed, those two one-line methods are the only code in the DialogProgram subclass.
Depending on your perception of what properties make a program object-oriented, the code for the Add2 example presented earlier in this chapter may not seem to meet those requirements. The method calls in the body of the run method have no explicit receivers, depending instead on the fact that the Program class itself implements the IOModel interface and therefore defines methods like println and readInt. Such an approach is simpler for novicesas well as for teachers with little background in the object-oriented style of programmingbut it doesnt emphasize the idea of sending messages to objects. It is possible, however, to rewrite the program so that make those object references explicit. If an instructor chooses to have all method calls be tied to an object, the run method for Add2 could be rewritten like this:
|
Judging from the range of opinions on the Task Force, different instructors will have strong preferences for one or the other of these paradigms, and it does not seem wise to allow only one of these styles. The decision to support both approaches is also consistent with the first principle put forward in Chapter 2, in which we make a commitment to ensure that our library packages and tools are usable with other pedagogical approaches besides the objects-first approach.
The GraphicsProgram subclass simplifies the use of the acm.graphics package from Chapter 5 and is quite straightforward in its design. During initialization, a GraphicsProgram allocates a new GCanvas and installs it in the content pane of the Program window. Subclasses that extend GraphicsProgram can then use either of two strategies to gain access to that canvas:
The first paradigm is illustrated by the following program, which draws the string Hello, world. at the center of the program window:
|
The second paradigm is simpler but uses a seemingly less object-oriented style, given that the receivers of the messages are all implicit:
|
The GraphicsProgram class implements wrappers for all the methods in GCanvas, with the exception of the overloaded version of the add method that takes a Component instead of a GObject. That method would have an ambiguous interpretation, because there would be no way to differentiate the operation of adding a component to the GCanvas or adding that component to the content pane of the program. This method is unlikely to be used by very many students and can easily be invoked unambiguously by calling
|
The GraphicsProgram class supports several strategies for responding to mouse events:
|
then that method is called whenever the appropriate event occurs in the GCanvas. The argument is the point at which the event occurred, already translated into the real-valued coordinate space of the acm.graphics package. This model hides the details of mouse events and mouse listeners, which is either a strength or a weakness depending on your point of view. On the one hand, this model makes it easier for students to write interactive graphical programs early because so much of the underlying complexity is hidden. On the other, students who use this style will have to learn how standard Java listeners work at some later point in their studies.
Each of these event-handling models is illustrated by one of the code examples in Figures 6-5, 6-6, 6-7, and 6-8, which reimplement the object-dragging example from Chapter 5 using one of these models.
Figure 6-5. Object-dragging program using the addMouseListeners method
/* * File: DragObjects.java * ---------------------- * This implementation illustrates the technique of using the * addMouseListeners method to register the program itself as * a listeners for events in the underlying GCanvas. */ import java.awt.*; import java.awt.event.*; import acm.graphics.*; import acm.program.*; /** This class displays a mouse-draggable rectangle and oval */ public class DragObjects extends GraphicsProgram { /** Initializes the program */ public void init() { GRect rect = new GRect(100, 100, 150, 100); rect.setFilled(true); rect.setColor(Color.RED); add(rect); GOval oval = new GOval(300, 115, 100, 70); oval.setFilled(true); oval.setColor(Color.GREEN); add(oval); addMouseListeners(); } /** Called on mouse press to record the coordinates of the click */ public void mousePressed(MouseEvent e) { last = new GPoint(e.getPoint()); gobj = getElementAt(last); } /** Called on mouse drag to reposition the object */ public void mouseDragged(MouseEvent e) { if (gobj != null) { gobj.move(e.getX() - last.getX(), e.getY() - last.getY()); last = new GPoint(e.getPoint()); } } /** Called on mouse click to move this object to the front */ public void mouseClicked(MouseEvent e) { if (gobj != null) gobj.sendToFront(); } /* Private instance variables */ private GObject gobj; /* The object being dragged */ private GPoint last; /* The last mouse position */ } |
Figure 6-6. Object-dragging program using inner classes to specify the listeners
/* * File: DragUsingInnerClasses.java * -------------------------------- * This implementation illustrates the technique of defining * listeners as anonymous inner classes. */ import java.awt.*; import java.awt.event.*; import acm.graphics.*; import acm.program.*; /** This class displays a mouse-draggable rectangle and oval */ public class DragUsingInnerClasses extends GraphicsProgram { /** Initializes the program */ public void init() { GRect rect = new GRect(100, 100, 150, 100); rect.setFilled(true); rect.setColor(Color.RED); add(rect); GOval oval = new GOval(300, 115, 100, 70); oval.setFilled(true); oval.setColor(Color.GREEN); add(oval); GCanvas canvas = getGCanvas(); canvas.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { last = new GPoint(e.getPoint()); gobj = getElementAt(last); } public void mouseClicked(MouseEvent e) { if (gobj != null) gobj.sendToFront(); } }); canvas.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { if (gobj != null) { gobj.move(e.getX() - last.getX(), e.getY() - last.getY()); last = new GPoint(e.getPoint()); } } }); } /* Private instance variables */ private GObject gobj; /* The object being dragged */ private GPoint last; /* The last mouse position */ } |
Figure 6-7. Object-dragging program that listens to the GObjects
/* * File: DragUsingGObjectEvents.java * --------------------------------- * This implementation illustrates the technique of assigning * listeners to GObjects. */ import java.awt.*; import java.awt.event.*; import acm.graphics.*; import acm.program.*; /** This class displays a mouse-draggable rectangle and oval */ public class DragUsingGObjectEvents extends GraphicsProgram { /** Initializes the program */ public void init() { GRect rect = new GRect(100, 100, 150, 100); rect.setFilled(true); rect.setColor(Color.RED); rect.addMouseListener(this); rect.addMouseMotionListener(this); add(rect); GOval oval = new GOval(300, 115, 100, 70); oval.setFilled(true); oval.setColor(Color.GREEN); oval.addMouseListener(this); oval.addMouseMotionListener(this); add(oval); } /** Called on mouse press to record the coordinates of the click */ public void mousePressed(MouseEvent e) { last = new GPoint(e.getPoint()); } /** Called on mouse drag to reposition the object */ public void mouseDragged(MouseEvent e) { GObject gobj = (GObject) e.getSource(); gobj.move(e.getX() - last.getX(), e.getY() - last.getY()); last = new GPoint(e.getPoint()); } /** Called on mouse click to move this object to the front */ public void mouseClicked(MouseEvent e) { GObject gobj = (GObject) e.getSource(); gobj.sendToFront(); } /* Private instance variables */ private GPoint last; /* The last mouse position */ } |
Figure 6-8. Object-dragging program that uses callback methods
/* * File: DragUsingObjectDrawModel.java * ----------------------------------- * This implementation illustrates the technique of using callback * methods in the style of the objectdraw package. */ import java.awt.*; import acm.graphics.*; import acm.program.*; /** This class displays a mouse-draggable rectangle and oval */ public class DragUsingObjectDrawModel extends GraphicsProgram { /** Initializes the program */ public void init() { GRect rect = new GRect(100, 100, 150, 100); rect.setFilled(true); rect.setColor(Color.RED); add(rect); GOval oval = new GOval(300, 115, 100, 70); oval.setFilled(true); oval.setColor(Color.GREEN); add(oval); } /** Called on mouse press to record the coordinates of the click */ public void mousePressed(GPoint pt) { last = pt; gobj = getElementAt(last); } /** Called on mouse drag to reposition the object */ public void mouseDragged(GPoint pt) { if (gobj != null) { gobj.move(pt.getX() - last.getX(), pt.getY() - last.getY()); last = pt; } } /** Called on mouse click to move this object to the front */ public void mouseClicked(GPoint pt) { if (gobj != null) gobj.sendToFront(); } /* Private instance variables */ private GObject gobj; /* The object being dragged */ private GPoint last; /* The last mouse position */ } |
Even though the acm.graphics package supports each of these models, it would almost certainly be a mistake to teach all four options to students, particularly at the introductory level. Instead, it makes pedagogical sense to presents only one of these strategies so that students are not confused by multiple approaches for solving similar tasks. The reason for providing the different mechanisms is to support different teaching styles, and there was no consensus as to which approach would be best for novices. Each of the strategies has distinct advantages and disadvantages, and we therefore thought it was prudent to support each strategy and to let individual instructors choose a style appropriate to their approach to the material.
From the feedback we have received from users who have worked with the beta version of the acm.graphics package, it is clear that the strategy of using the program as the listener for mouse events works well at the introductory level. Based on that successful experience, we decided to add a parallel facility to support keyboard events. Calling addKeyListeners() in a GraphicsProgram adds the program as a key listener to the embedded canvas.
Much of the development work between the first and second draft releases of the Java Task Force packages was directed toward finding strategies to simplify the creation of graphical user interfaces, or GUIs. The results of that work and the rationale that led up to it are outlined in Chapter 7, which describes the acm.gui package. One aspect of that design, however, seemed to fit more easily in the acm.program package, because it is logically associated with the Program class itself. The essential idea behind the design is that programs often need to be able to create simple control bars that present the user with a simple list of interactors. Unfortunately, given the facilities provided by the standard Java packages, building this type of control bar and populating it with interactors is often too difficult for novices. As at least a partial solution to this problem, the Java Task Force decided to implement a new layout manager for programs whose primary function is to simplify the creation of control bars.
The Program class automatically assigns a BorderLayout manager to the content pane and initializes each of the five componentsNORTH, SOUTH, EAST, WEST, and CENTERto be an empty JPanel. The individual JPanel objects are arranged as in the standard BorderLayout paradigm, which looks like this:
Each of the regions along the edges is assigned its preferred size, which means that it disappears entirely as long as it is empty. The center region gets all the remaining space.
The Program class overrides the standard definitions of the add methods so that components are added to the center region by default, but to one of the side regions if the constraint matches the appropriate region constant. Thus, calling
6.5 Layout strategy for the Program class
|
will add a new button labeled OK to the SOUTH region.
As with any container, the components that makes up the regions of a Program object are rearranged whenever the container is validated, which happens automatically when the container is resized but which can also be triggered explicitly by calling validate on the container. The Program logic automatically calls validate after the init method returns, which means that most students will never have to worry about forcing an update of the layout structure.
Each of the
NORTH, SOUTH | new TableLayout(1, 0, 5, 5) | Single horizontal row. |
EAST, WEST | new TableLayout(0, 1, 5, 5) | Single vertical column. |
CENTER | new GridLayout(1, 0) | Equally spaced horizontal row. |
You can, however, change any of these layout managers by calling
|
The details of the TableLayout class are discussed in Chapter 7.
The most common use of the border regions defined by the Program class is to create a control strip by adding one or more
It is, of course, possible to adopt the style that has become commonplace in professional Java coding and assign an individual action listener to each button in the form of an anonymous inner class. For example, if you wanted to create a Test button whose effect was to invoke a method called testAction, you could do so by writing the following code:
|
Although there are certainly instructors who favor this style even at the level of introductory courses, the members of the Task Force were concerned that such code introduces too many unfamiliar concepts for novice programmers to comprehend. To simplify the structure and eliminate the use of inner classes, the Task Force chose to define the Program class itself as an action listener and allow students to define the actionPerformed method as part of their program code. Adopting this strategy means that the definition of the button can be shortened to
|
The actual dispatch is specified in a separate actionPerformed method, as follows:
|
This single actionPerformed method is shared among all the buttons defined in this way, which means that it must perform internal tests to determine which button has been activated. That determination can be made either by looking at the action command (as shown here) or by calling e.getSource to obtain the identity of the button that triggered the event.
As a further simplification, the Program class includes a method called addActionListeners, which recursively traverses the components displayed on the screen and adds the program as an action listener for every JButton it encounters. This style is illustrated in the examples shown in the section that follows.
The code in Figure 6-9 offers a simple example of the layout strategy provided by the Program class. The code defines three buttons that simply display their name on the console whenever they are activated.
Figure 6-9. A ConsoleProgram version of a stoplight
/* * File: StoplightConsole.java * --------------------------- * This program illustrates the construction of a simple GUI. */ import acm.program.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; /** * This class displays three buttons at the south edge of the window. * The name of the button is echoed on the console each time a button * is pressed. */ public class StoplightConsole extends ConsoleProgram { /** Initialize the buttons. */ public void init() { add(new JButton("Green"), SOUTH); add(new JButton("Yellow"), SOUTH); add(new JButton("Red"), SOUTH); addActionListeners(); } /** Listen for a button action. */ public void actionPerformed(ActionEvent e) { println(e.getActionCommand()); } } |
The three calls to add in the constructor assign these buttons to the SOUTH region of the layout, as specified by the constraint parameter in the add call. Because the default layout for the SOUTH region is a TableLayout manager with a single row and a five-pixel gap between adjacent interactors. The result is therefore a program window that looks like this, which shows the state of the console window after the user has clicked each of the buttons in order:
Positioning interactors around the border is equally useful with Program subclasses other that ConsoleProgram. The code on the web site for the stoplight program also includes a graphical version that makes the program a bit more interesting.
The Edit menu includes items for the Cut, Copy, Paste, and Select All options, all of which are standard in modern computing platforms.
Figure 6-10. Menus provided by the standard Program class
|
|
In order to create a different menu bar, the application programmer can override this method so that it returns a different ProgramMenuBar, which will typically be an instance of a programmer-defined subclass of the base class. For example, a specific application program could redefine the factory method as
|
With this definition, the program would create its menu bar by constructing an instance of MySpecialMenuBar instead.
The ProgramMenuBar class is a subclass of the standard Swing JMenuBar class. You can add
Figure 6-11. Standard code to initialize a ProgramMenuBar
As an example, suppose that you were developing a program called SpellCheckingEditor and wanted to include a Spell Check item in the Edit menu. To do so, you could override the addEditMenuItems method like this:
In addition to illustrating how items can be added to a menu, this code also shows how the programmer can specify the response to the menu item. As in the standard menu-handling paradigm in Swing, the strategy is to add an action listener to the menu item. When the menu item is activated, the associated action listener is sent an actionPerformed method. In this case, actionPerformed initiates a call to the spellCheck method in the SpellCheckingEditor program that controls the menu bar. Implementation issues with respect to menu barsMenu bars proved to be one of the most difficult aspect of the acm.program package to get right. In many ways, the overarching design criterion for menu bars is that they should behave so intuitively that the user does not have to think about them at all. Unfortunately, achieving this goal means that menu bars must be implemented in a system-dependent way because each platform has a particular notion of what constitutes intuitive behavior. On Microsoft Windows and most flavors of Unix that support window systems, the menu bars are included in the program frame. On the Apple Macintosh, the menu bar appears at the top of the entire screen. In that context, having two menu barsone for the window and one for the applicationonly generates confusion. When a program is running as an applet, the appropriate structure for a menu bar depends on the capabilities of the browser.At the same time, it is essential to shield that platform dependence from programmers who are coding at the novice level. The strategy that we eventually chose hides that complexity in the code for the ProgramMenuBar class. On most systems for which a JMenuBar is the appropriate structure, the Program class simply installs the menu bar in the appropriate place in the JFrame used in the application. On the Macintosh, the Program class installs an AWT MenuBar instead but ensures that the effect of that MenuBar is what the application designer expects. Each MenuItem in the AWT MenuBar listens to the corresponding JMenuItem in the ProgramMenuBar and changes when that item changes. Thus, if the programmer enables or disables a menu item in the ProgramMenuBar, that change will be reflected in the Macintosh menus as well. 6.7 Other features of the Program classThe Program class includes a few important features beyond those outlined in the preceding sections. Although each of these features was mentioned briefly at the end of the introduction to this chapter, it is useful to describe them in more detail.
One of the most important simplifications offered by the Program class is the elimination of the static main method that has been cited by so many instructors as a source of difficulty. As noted in section 6.4, the Program class includes its own public static void main method, which students therefore do not have to write. The implementation of the main method in the Program class executes the following steps:
At the time of the February 2005 release of this rationale document, the Task Force believed that it was not possible to implement this strategy on all of the major platforms. The only problematic step is the first: determining the identity of the class that the user intended to run, which we will identify as the main class. While this problem might seem straightforward, the current design of Java provides no platform-independent mechanism to do so. Fortunately, there are platform-specific mechanisms that work for Microsoft Windows, Mac OS X, and Linux, which are the primary platforms we expect adopters to use. The current implementation of the Program class supports each of these platforms. If you are using a platform not included in this list or are working with an Integrated Development Environments (IDE) that has not been updated to support the Java Task Force packages, you may have to adopt a slightly more cumbersome strategy. Even before attempting its platform-dependent algorithm to identify the main class, the Program initialization code parses the arguments passed to main. Any parameter that has the form
is interpreted as a parameter definition and made available to the program through the getParameter method defined by the Applet class. The parameters that appear in the HTML APPLET tag are treated specially at startup time and can be used to designate not only the main class but other such useful parameters as the width and height of the window. These parameters can be passed explicitly on the command line like this:
This strategy can also be used in IDEs that make it possible to pass command-line arguments to the program being invoked. In those environments, the instructor can provide students with assignment templates that include the code parameter. If the strategy of passing parameters does not work, it is always possible to add an explicit public static void main method to the Program code. The code is entirely canonical and looks like this:
where MainClass is the name of the main class. Including this method ensures maximum portability but significantly increases the conceptual overhead necessary for students to write their first programs. We have also taken steps to ensure that this problem can eventually be solved in a much cleaner way. Together with Cay Horstmann, we submitted a request to the Java Community Process to include a new property called java.main that can be obtained through System.getProperty. The current implementation of the Program class already looks for this property. When it is implemented, a great deal of unnecessary code can be eliminated. Setting the size and location of the application windowIn an applet, the size of the window is determined by the width and height parameters to the applet tag in an HTML file. Applications, however, have no direct counterpart. The traditional strategy for building applications requires the programmer to set the bounds explicitly with a setSize or setBounds call applied to the Frame or JFrame object that encloses the application. When one is using the acm.program package, setting up that frame is under the control of the Program startup code, which suggests that a different approach is necessary to setting size and location. The Program startup code chooses the size and location of the frame by applying the following strategies in order:
Applet capabilitiesBecause Program is an indirect subclass of Applet, programs have access to the methods provided by Java applets even when they run as applications. Of these capabilities, the most useful are the following:
6.8 Strategies for using the Program classOne of the most important learning experience to come out of the Task Force was that different people teaching introductory computer science have widely divergent views about how to teach programming in an object-oriented language such as Java. Many instructors put off any significant coverage of objects until late in the introductory course, after the students have already learned to use the more traditional procedural paradigm. Even among those who believe that it is important to introduce objects earlythe curriculum strategy that Computing Curricula 2001 The central point of contention is which aspects of object-oriented programming need to be brought out early, and which can be deferred. Some instructors, for example, believe that it is important to emphasize the notion of message passing and, in particular, to ensure that students are comfortable with the receiver-based syntax used in object-oriented languages to invoke a method on an object:
For instructors who support this view, the specification of a receiver object is the key concept to learn early. Some instructors emphasize the role of the receiver by requiring students to specify this when an object makes calls to its own methods. Another group of instructors holds that the critical concept to present early is the overall hierarchical structure of objects and classes. In this view, students should learn early the difference between objects and classes along with the idea that objects of one class are also objects of its various superclasses. This contingent tends to present examples of inheritance early, emphasizing the notion that subclasses extend the behavior of their superclasses. By contrast, instructors in the community that focuses on message-passing typically regard inheritance as less essential in the early part of the term. Although strong opinions were expressed within the Task Force on both sides of this debate, it seems clear that each model has been used successfully to introduce object-oriented programming to students. It would not make sense for the Task Force to endorse one model over the other. Instead, it is important for the ACM packages to support each of these models well and enable students to use either style from the very beginning of the course. The following section offers several implementations of a flower-drawing program that illustrates each of these styles. A simple graphical example: Drawing a flower using GTurtleDuring the first weeks of an introductory programming course, many instructors introduce some sort of microworld, such as the LOGO system described by Seymour Papert in Mindstorms Figure 6-12. A single-method program that draws a turtle flower
Although this implementation illustrates the idea of sending a message to a GTurtle object and gives students practice with for loops, it takes no advantage of the power of decomposition, which is one of the fundamental programming principles that microworlds make so easy to illustrate. Unfortunately, breaking the run method down into components is not as simple as it might appear. The primary issue you need to resolve is how to share the GTurtle object with the subsidiary methods. Passing it as a parameter to each level has stylistic advantages but seems likely to add more complexity than novice programmers can easily assimilate. The other obvious approach is to declare turtle as an instance variable for the class, which gives rise to the code in Figure 6-13. Figure 6-13. A first attempt at decomposing the DrawTurtleFlower program
The problem with this coding is that the structure seems unnatural to programmers who are predisposed to an early introduction of inheritance. Conceptually, this program defines a turtle. You tell the turtle to turn left by writing
but draw a square by writing
The asymmetry is confusing. Why isnt drawSquare also a message to the turtle in the way that left is? The answer, of course, is simply that the methods drawFlower and drawSquare are defined as methods in the DrawTurtleFlower class and not in the GTurtle object stored in the variable turtle. That object knows how to respond to the left message but not to the drawFlower message. It is, however, easy enough to reorganize the program so that the main program instantiates a new FlowerTurtle subclass that has these capabilities. That program appears in Figure 6-14. Note that the new coding no longer requires an instance variable and that it has a consistent calling structure. The DrawTurtleFlower class creates a new FlowerTurtle object and sends it the drawFlower message using the receiver-based syntax turtle.drawFlower(); Inside the definition of FlowerTurtle, however, all methods are invoked without an explicit receiver object, because they operate on the current instance of FlowerTurtle. Some instructors regard this coding as emblematic of good object-oriented design; others regard the absence of receivers as a sure indication that the program is not really object-oriented at all. The point is that the two coding styles are likely to appeal to instructors with different emphases, and it is important for the Java Task Force to support both constituencies. Figure 6-14. A DrawTurtleFlower implementation using a FlowerTurtle subclass
Using GTurtle subclasses as standalone programsAlthough the code in Figure 6-14 offers an example of how inheritance can be employed in a useful way, it may be beyond the level of what one can teach at the very beginning of an introductory course. If nothing else, the program contains two separate classes: the DrawTurtleFlower class that acts as the main program and FlowerTurtle that extends GTurtle so that it can draw a flower. Its hard enough to get new students to understand a single class at the beginning of the course, and introducing two might send some over the edge. To make this style of coding easier for students and teachers to use, the GTurtle class includes a special hook that allows it to operate as a standalone program. If a GTurtle or one of its subclasses is specified as the main class, its implementation of main starts up the program by performing the following operations:
This interpretation provides a very simple framework for creating animated graphical programs in the first few days of the term, as shown in Figure 6-15. Figure 6-15. Using the FlowerTurtle subclass as a standalone program
6.9 The decision to use the Applet modelAs the discussion in this chapter emphasizes, the Program class attempts to combine the best features of applications and applets and to make the facilities of both mechanisms available to students. Most development environments today support the application paradigm, but the ability to run programs as applets offers the obvious advantage of making it possible to run programs on the web. That capability makes it easier for the Java Task Force to create demonstration programs, but also holds forth the promise of enabling students to make their programs easily available to friends and family. Despite these advantages, the decision to use the applet paradigm flies in the face of the direction that Java seems to be taking. Support for applets is eroding in the Java community, as evidenced by the fact that Suns Java tutorial no longer uses applets for its demos. In light of this decline in the fortunes of the applet paradigm, it is important for us to outline why we have chosen to embrace applets in the design of the acm.program package. The sections that follow recount the evolution of that decision and offer several reasons why we believe that this strategy will provide a stable approach to the problem of creating web-accessible programs. The rise and fall of the applet paradigmEver since its introduction in 1995, educators have recognized that Java represents an attractive environment for the development of interactive teaching materials, primarily because of its integration with the web through the applet mechanism. The ITiCSE conferences in both 1997 and 1998 included working groups seeking to develop Java as a resource for creating visualizations and interactive animations for instructional use Visualization has long been an important pedagogical tool in CS education. The widespread use of the Web and the introduction of Java, with its ability to present interactive animated applets and other types of animation, all provide opportunities to expand the availability of visualization-based teaching and learning. In recent years, however, the use of web-based resources in Javaand particularly those based on the applet paradigmhave declined in popularity as it became more difficult to maintain compatibility among applets running in an ever-expanding array of browsers that often implement radically different versions of the Java Development Kit (JDK). These compatibility problems have caused applets to fall out of favor in the Java community, where they have been replaced in many environments by plug-ins that implement the Java virtual machine or by the Java Web Start technology supported by Sun Java applets fueled Javas initial growth. The ability to download code over the network and run it on a variety of desktops offering a rich user interaction proved quite compelling. However, Javas Write Once, Run Anywhere (WORA) promise soon became strained as browsers began to bloat and several incompatibilities emerged that were caused by the Java language itself. The strategy of using Java Web Start or browser-specific plugins does not represent a real solution to the problem of browser incompatibility. Java Web Start, for example, is not implemented on all platforms and, even when implemented, typically requires the user to download and install the appropriate plug-in. More importantly, Java Web Start applications run only if the version of the Java runtime on the users system is up to date with respect the web-based code. In a sense, the abandonment of the applet paradigm has not fostered any increase in compatibility but merely made it possible for both Sun Microsystems and the browser vendors to declare those incompatibilities to be, in effect, someone elses problem. What has changed is that there is no longer an expectation that applets will work compatibly in different browsers, forcing the adoption of an alternate strategy. Making applets workThe reality of the situation with regard to applets, however, is not quite as gloomy as the preceding section implies. Although it is impossible to ensure that any Java applet will run correctly in all browsers, it is possible to engineer specific applets so that they run acceptably well in most of them. The difficulty, however, lies in the fact that making applets run in a wide range of browsers forces one to adopt a lowest-common-denominator strategy that runs counter to the modern approaches to Java that one wants to offer to students. To illustrate the severity of the problem, it is still the case that almost any browser you encounter in a commercial Internet-access storefront will be running the 1.1 version of the JDK. If you walk into your local Kinkos outlet or the nearest easyInternetCafe, JDK 1.1 is all you find. Given that JDK 1.2 was released in December 1998, these browsers are more than eight years out of date, which is an eternity in the fast-paced world of computing technology. JDK 1.1 lacks support for Swing, for collections, and for many other tools that modern Java programmers cannot live without. The cost of living in that world seems to far outweigh any possible benefits. It is, of course, quite legitimate to ask why anyone would care about old browsers such as those in Internet storefronts. That is not, presumably, where most students work, nor is it the place from which prospective adopters are most likely to look at the Java Task Force materials. Anyone who is prepared to teach Java must be willing to do so in a more modern Java environment, and it would be wrong to encourage them to do otherwise. That view, however, may be too optimistic. While it is unlikely that students in a well-endowed university find themselves faced with obsolete technology, the same cannot be said for schools in low-income areas or institutions that focus on distance-learning and consequently have less control over the hardware and software environments their students use. In those environments, it is not at all unlikely that someone will boot up their machine, invoke the browser that comes for free on the desktop, and discover that it doesnt run any of their programs. The failure mode associated with running a new applet in an old browser is also particularly bad from a pedagogical point of view. The applet simply fails to load, and the error messageif it is visible at alltends to be entirely unhelpful. But there is yet another consideration that militates in favor of allowing programs to run in older environments. One of the great things about allowing student programs to run as applets is that doing so permits them to share those programs easily with relatives and friends. Think for a moment about a student from the inner city, the first in the family to attend college, who takes an introductory computing course and completes an assignment that generates a great deal of pride. Such students should be encouraged to put their programs up on the web and to tell their parents about them. The parents may not have access to the web outside of a Kinkos or a public library, and might very well be unable to obtain a better, more modern browser. If the students applet runs in that environment, the parents will be able to see it; if not, they are out of luck. Evolution of the Java Task Force strategyAs noted in Chapter 1, the February 2005 release of the Java Task Force materials used only JDK 1.1 constructs in the implementation of the library itself, thereby making it possible for applets built using the libraries to run in the most commonly available browsers. This approach generated considerable criticism. Much of that criticism reflected the mistaken belief that this decision in some way constrained adopters to adhere to the same restrictions. In fact, the initial implementation of the packages did take advantage of JDK 1.2 features if they were available. If they were not, the packages tried to do something reasonable to ensure that the programs would run on old browsers that did not support those features. At its meeting in June 2005, the Task Force decide that it had to abandon that position and adopt a more modern Java baseline for its own implementation. After some deliberation, we chose JDK 1.4 as the appropriate compromise in that it supports more of Java than our earlier design but does not insist on a version that is still unsupported on many common platforms. Our reasons for making that decision were as follows:
At the same time, we did not wish to abandon the goal of allowing the JTF packages to function in the widest possible set of browsers. In particular, we wanted to code the libraries using JDK 1.4 but somehow have it run in JDK 1.1 browsers. At first glance, this goal sounds impossible. One possible approach that might have succeeded was to develop two versions of the acm.jar library, one for use with JDK 1.4, and one for use with JDK 1.1 to provide legacy browser support. That strategy, however, requires maintaining two separate versions of the library code, which would represent a maintenance nightmare. We regarded that strategy as unacceptable and did not pursue it. The best of both worldsAfter giving up on the idea of supporting legacy browsers on several occasions, we eventually hit upon a strategy that provides exceptional browser compatibility while allowing a modern coding style. Although it at first seems difficult to imagine, the strategy offers all of the following advantages:
The key to making this strategy work lies in providing additional classes that implement simplified versions of the JDK 1.4 classes on which the library depends. If the program and libraries are compiled with these additional classes, the resulting code runs on JDK 1.1 browsers. If the programs and libraries are compiled alone, the program relies instead on the system implementation of these classes. Operationally, students can include the compatibility code by compiling their programs with the acm11.jar file instead of the standard acm.jar. Because the acm11.jar file includes the additional classes designed to ensure backward compatibility, the resulting program runs in JDK 1.1 environments. The essential character of this strategy is easiest to illustrate by example. Suppose that a student wants to include a button in an application. In any modern version of Java, that button will be implemented using Swings JButton class. Unfortunately, JDK 1.1 browsers dont know about the JButton class, which was introduced in JDK 1.2. It is hardly appropriate to suggest that students use the older Button class instead, because doing so would require them to use a Java paradigm that has long been obsolete. On the other hand, it is perfectly acceptable for students to write their code using JButton but to provide that class in the acm11.jar file. The students code is written precisely as it should be, but compatibility can nonetheless be maintained by simulating the JButton class in terms of more primitive AWT features. The acm11.jar file contains implementations for every class that is defined in Chapter 9 as part of the JTF subset but which is not already part of JDK 1.1. These classes include the collection classes in the java.util package and the most common interactors defined in javax.swing. It does not, however, need to implement those features of Swing that lie outside the JTF subset. The resulting simplification turns out to be enormous. The code to accomplish the goal of simulating the new classes in the JTF subset, while not at all trivial, is surprisingly small. Given that the subset includes only a very small fraction of the classes involved in the Swing domain, the basic functionality of those classes can be simulated using less than two percent of the source code required for the full implementation of Swing. In point of fact, this strategy is not quite as easy to implement as it sounds. The new classes defined as part of the acm11.jar file cannot actually live in the java and javax package hierarchy because the security manager for applets ordinarily makes it illegal to load user classes into those packages. (Being able to define new classes in system packages would indeed represent a significant security loophole in that such classes would have access to the protected information for that package.) Thus, the new classes provided by acm11.jarsuch as the backward-compatible implementation of JButtonhave to live somewhere else. At first glance, that restriction suggests that the users code would need to include additional import statements to gain access to those classes, which would violate the principle that there be only one version of the source. In actuality, the problem is even worse than that. If the acm11.jar file did assign the compatibility classes to a new package, the references to those classes would typically ambiguous, given that a JButton class, for example, would then exist in both javax.swing and the package containing the compatibility classes. There is, however, a simple stratagem that avoids both the ambiguity and the need for additional import statements. If the compatibility classes are defined in the same package as the one in which the reference occurs, the compiler will use those definitions before looking at the imports. The acm11.jar library therefore defines the set of classes required for compatibility as part of the unnamed package typically used in introductory courses. If a student program is compiled using the acm.jar library, any references to JButton will be resolved to the javax.swing package. If the program is instead compiled with acm11.jar, references to JButton will be resolved within the unnamed package. The acm11.jar uses a similar strategy to ensure that the code for the ACM packages themselves have access to the full set of classes in the recommended JTF subset. If one of the ACM packages uses a class that is not available in JDK 1.1, the acm11.jar defines a class with that name as part of the appropriate ACM package to ensure that the code will run on any JDK 1.1 browser.
|