User interfaces for object-oriented systems, Part 6: The RPN calculator

The RPN-calculator application demonstrates object-oriented UI principles

1 2 3 4 5 6 Page 2
Page 2 of 6

Let's now move on to the Menu_site. The Menu_site interface contains an inner-class implementation called Menu_site.Implementation. Normally, a class could be a menu site simply by extending this inner class, but that's not an option here because our one and only extends relationship is already used up by the JFrame base class. Consequently, the Rpn class (Listing 1) implements the Menu_site interface's methods as simple pass-throughs, which do nothing but chain through to the equivalently named methods of the Menu_site_support object: that is, support; see Listing 1, line 49). These pass-through methods are at the very end of Listing 1.

This way of doing things -- implementing an interface with pass-through methods that chain to methods of an implementation class -- is a common enough idiom that it ought to be an official design pattern, but as far as I know, it's as yet unnamed. You can use this mechanism to implement true multiple inheritance in Java: from the perspective of a user of Rpn objects, Menu_site effectively acts not as an interface but as a base class that contains an implementation. Rpn really uses a paired interface/implementation class, however. This design pattern, then, lets you implement multiple inheritance with all the flexibility of C++, for example, without any of the potential ambiguity problems implicit in C++'s inheritance mechanism.

Listing 1 (/src/rpn/Rpn.java): Implementation of the analysis-level model
   1: package rpn;
   2: 
   3: import java.awt.*;
   4: import javax.swing.*;
   5: import java.awt.event.*;
   6: 
   7: import com.holub.ui.Menu_site;
   8: import com.holub.ui.Scrollable_JTextArea;
   9: import com.holub.tools.debug.Assert;
  10: 
  11: /** © 2000, Allen I. Holub. All rights reserved.
  12:  *
  13:  * Rpn.java application is a simple RPN calculator. Its UI
  14:  * consists of two windows, one showing the stack and another showing
  15:  * the current input string. Type an RPN expression into the input
  16:  * window and the results appear in the stack window. Type "?" to
  17:  * display a help window. A third window displays a financial-style
  18:  * "tape" that shows a history of what you've done.
  19:  *
  20:  * This application nicely demonstrates the visual-proxy
  21:  * architecture. The presentation layer is created from visual
  22:  * proxies provided by abstraction-layer objects (parser and stack).
  23:  * Each of these objects is responsible for updating its own UI.
  24:  * The Parser object doesn't even know that the stack
  25:  * displays a user interface, much less what the presentation
  26:  * looks like; rather, it manipulates the "abstraction" directly and
  27:  * the "abstraction" takes care of its own UI. All proxies are
  28:  * declared "JComponent"s at this level, so the details of how
  29:  * they work really are hidden.
  30:  *
  31:  * The current application is easily turned into an applet by
  32:  * extending java.awt.Applet rather than java.awt.Frame, and
  33:  * overriding the appropriate applet methods. You will need to get
  34:  * rid of the System.exit() call in the WindowAdapter created by the
  35:  * constructor, however.
  36:  *
  37:  * @author  Allen I. Holub
  38:  * @version 2.01
  39:  */
  40: 
  41: 
  42: public class Rpn extends    JFrame
  43:                             implements  Menu_site
  44: {
  45:   private final Math_stack math_engine   = new Math_stack( 512    );
  46:   private final Parser     parser        = new Parser  ( math_engine );
  47:   private       JComponent parser_viewer; // = null
  48: 
  49:   private final Menu_site.Implementation support
  50:                                 = new Menu_site.Implementation(this);
  51: 
  52:   private static final String[] about_message =
  53:     {
  54:     "      RPN Calculator ver. 2.01. © 2000 Allen I. Holub.",
  55:     "",
  56:     "This application was downloaded from Allen Holub's Website:",
  57:     "",
  58:     "                http://www.holub.com",
  59:     "",
  60:     "where you'll find information about Java and-object-oriented training",
  61:     "and other Java-related goodies. The .class files that",
  62:     "comprise this application may be distributed freely for",
  63:     "noncommercial purposes, provided that they are distributed",
  64:     "without modification.",
  65:     "",
  66:     "The source code for this application may not be redistributed.",
  67:     "Period.",
  68:     "",
  69:     "Usage: java [-Dlog.file=log_file_name] rpn.Rpn"
  70:     };
  71: 
  72:     /************************************************************
  73:      * Creates an RPN calculator. If any command-line arguments are
  74:      * found, suppresses the normal banner window.
  75:      */
  76:   public static void main(String[] args)
  77:     {   
  78:         Rpn the_calculator = new Rpn();
  79:         if( args.length <= 0 )
  80:             JOptionPane.showMessageDialog(null, about_message,
  81:                                     "About RPN Calculator",
  82:                                     JOptionPane.INFORMATION_MESSAGE );
  83:     }
  84: 
  85:     /************************************************************
  86:      * Calculator is a PAC "Control" class. The constructor
  87:      * first sets up its own visual environment
  88:      * by setting up the Menu_site and installing a few basic
  89:      * menu items (File:Exit and Help:About). (Other menu items
  90:      * will be added by the UI proxies as they are activated.)
  91:      *
  92:      * It then creates the two logical-model
  93:      * ("abstraction") entities (the Math_stack and Parser) that
  94:      * comprise the current application.
  95:      * 
  96:      * Finally, the control objects set's up the UI by
  97:      * requesting UI proxies ("presentations") from the two
  98:      * abstraction-level classes and installing them in the
  99:      * Calculator's frame and doing the necessary stuff to get
 100:      * the focus to the correct UI proxy.
 101:      */
 102:   public Rpn()
 103:     {
 104:         super       ("RPN Calculator"    );
 105:         setBounds   ( 200, 200, 200, 325 );
 106: 
 107:         // Set up the File and Help menus
 108: 
 109:         JMenu file = support.menu("File");
 110:         file.add( support.line_item
 111:                   ( "Exit",
 112:                     new ActionListener()
 113:                   {   public void actionPerformed(ActionEvent e)  
 114:                         {   System.exit(0);
 115:                         }
 116:                     }
 117:                   )
 118:                 );
 119: 
 120:         JMenu help = support.menu("Help" );
 121:         help.add( support.line_item
 122:                   ( "About RPN Calculator",
 123:                     new ActionListener()
 124:                   {   public void actionPerformed(ActionEvent e)  
 125:                         {   JOptionPane.showMessageDialog(
 126:                                     Rpn.this,
 127:                                     about_message,
 128:                                     "About RPN Calculator",
 129:                                     JOptionPane.INFORMATION_MESSAGE );
 130:                         }
 131:                     }
 132:                   )
 133:                 );
 134: 
 135:         add_menu( this, file );
 136:         add_menu( this, help );
 137: 
 138:         // Get the user interfaces provided by the parser and
 139:         // stack and arrange for the focus to stay in the Parser's
 140:         // UI at all times.
 141: 
 142:       JComponent stack_viewer = math_engine.visual_proxy("", false);
 143:         parser_viewer           = parser.     visual_proxy("", false);
 144:         
 145:         stack_viewer.addFocusListener
 146:         (   new FocusAdapter()
 147:           {   public void focusGained(FocusEvent e)
 148:                 {   parser_viewer.requestFocus();
 149:                 }
 150:             }
 151:         );
 152:         stack_viewer.addComponentListener
 153:         (   new ComponentAdapter()
 154:           {   public void componentResized(ComponentEvent e)
 155:                 {   parser_viewer.requestFocus();
 156:                 }
 157:             }
 158:         );
 159: 
 160:         // Display the parser and stack user interfaces in the
 161:         // calculator's frame.
 162: 
 163:         JSplitPane splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
 164:         splitter.setTopComponent( stack_viewer );
 165:         splitter.setBottomComponent( parser_viewer );
 166:         getContentPane().add( splitter );
 167: 
 168:         // Arrange for the application to shut down when the main
 169:         // frame is closed. Also arrange for focus to be transferred
 170:         // to the Parser's UI when the main frame is activated.
 171: 
 172:         this.addWindowListener
 173:         (   new WindowAdapter()
 174:           {   public void windowClosing(WindowEvent e)
 175:                 {   System.exit(0);
 176:                 }
 177:               public void windowActivated(WindowEvent e)
 178:                 {   parser_viewer.requestFocus();
 179:                 }
 180:             }
 181:         );
 182: 
 183:         show();
 184:         parser_viewer.requestFocus();
 185:     }
 186:     /************************************************************
 187:      * Delegates menu-site operation to the similarly named method
 188:      * in the Menu_site.Support class.
 189:      * @see Menu_site
 190:      */
 191:   public void add_menu(Object requester, JMenu item)
 192:     {   support.add_menu(requester, item);
 193:     }
 194:     /************************************************************
 195:      * Delegates menu-site operation to the similarly named method
 196:      * in the Menu_site.Support class.
 197:      * @see Menu_site
 198:      */
 199:   public void 
 200:     add_line_item(Object requester, JMenuItem item, String to_this_menu)
 201:     {   support.add_line_item(requester, item, to_this_menu );
 202:     }
 203:     /************************************************************
 204:      * Delegates menu-site operation to the similarly named method
 205:      * in the Menu_site.Support class.
 206:      * @see Menu_site
 207:      */
 208:   public void remove_my_menus(Object requester)
 209:     {   support.remove_my_menus(requester);
 210:     }
 211: }
         

The Math stack

Now let's look at the classes that make up the abstraction-layer "model," starting with the Math_stack (Listing 2). The UML version of the static model is in Figure 7. The stack is made up of an array of doubles (stack: see Listing 2, line 51) and the stack pointer (sp: see Listing 2, line 53).

The constructor controls the size of the array. The math engine also supports 10 registers (accessed by number), implemented by an array of doubles (Math_stack register: see Listing 2, line 48).

Figure 7. The Math_stack arithmetic engine

The vast majority of Math_stack's methods just implement stack-manipulation requests (push, pop, add, and so on), so they don't need further comment. The proxy maintenance is worth looking at, however. The stack must notify the proxies whenever it changes state so that they can redraw themselves. It does this by means of Swing's ActionListener interface. The Stack_viewer proxies (Listing 2, line 86) implement ActionListener, and the Math_stack object keeps a list of the proxies (the Stack_viewers) in an AWTEventMulticaster called stack_proxies (Listing 2, line 58).

All of the stack-manipulation methods (such as push) finish up with a call to update() (Listing 2, line 178), which passes an ActionPerformed message to the multicaster, which in turn relays the message proxies. The visual_proxy(...) method (Listing 2, line 148), when it creates the proxies, both manufactures the proxy object and adds it to the multicaster.

The Stack_viewer inner class (Listing 2, line 86) is a Scrollable_JTextArea (presented in January's Java Toolbox). In Stack_viewer, the actionPerformed(...) method (Listing 2, line 112) performs the only real work by redrawing and nicely formatting the text area. (The Align class, used to align the numbers on the decimal point, was also presented in January's Java Toolbox.)

Note that the Stack_viewer is an inner-class object, so it has direct access to the actual stack and sp fields in the Math_stack outer class, and it indeed accesses these fields directly. Though such a tight coupling does violate the integrity of the outer-class object, which makes it suspect from an object-oriented perspective, all proxies are inherently tightly coupled to the objects they represent.

If you change the object, you'll have to change the proxies too. Since the coupling is inevitable, there's no point in adding complexity (additional methods) to present the illusion of decoupling. In any event, nonstatic inner classes are members of the outer class in a very real sense, so it is reasonable for them to use the access privilege available to all members. The maintenance problem inherent in this tight coupling is obviated to some extent by the fact that the two classes being declared in the same place. Consequently, when you make a change to the outer class, it's easy to find all the other affected classes -- they're all inner classes of the outer class.

1 2 3 4 5 6 Page 2
Page 2 of 6