User interfaces for object-oriented systems, Part 3

The incredible transmogrifying widget

1 2 3 Page 3
Page 3 of 3
Listing 2 (/src/com/holub/ui/Bag.java): The Bag Wrapper
1:  package com.holub.ui;
2:  
3:  import java.awt.*;
4:  import java.awt.event.*;
5:  import java.util.*;
6:  
7:  import javax.swing.*;
8:  import javax.swing.event.*;
9:  import javax.swing.border.*;
10:  
11:  import com.holub.ui.AncestorAdapter;
12:  import com.holub.tools.Multicaster;
13:  
14:  /**
15:   *  The Bag is a Collection that supports a notion of a user interface.
16:   *  The Bag can produce a "visual proxy" that will take the form
17:   *  of a JList, JComboBox, or JButton (which displays a dialog when
18:   *  pushed), depending on the amount of screen real estate that's
19:   *  available to it. The proxy decides how to display itself without
20:   *  any outside intervention. Moreover, the "visual proxy" automatically
21:   *  reflects model-level changes such as adding or removing items from the list.
22:   *  For example, when you add or remove an item from a Bag, any proxies
23:   *  that have exposed UIs will update their UIs to indicate the change.
24:   *  
25:   *  <p>Unlike a Collection, a Bag also has a notion of the "top" element.
26:   *  (the one that you encounter first when you reach into the bag).
27:   *  The top element is typically modified by the user selecting
28:   *  an element in a UI created by a visual proxy. You can find out
29:   *  when that element changes by registering an ActionListener. You
30:   *  can also designate an existing bag element as the "top" element
31:   *  manually by calling {@link Bag#designate_top}.
32:   *  
33:   *  <p>As with standard collections, the Bag is not particularly thread 
34:   *  safe. It does take some trouble to make sure that the list of
35:   *  <code>ActionListener's</code> is handled in a thread-safe way, but
36:   *  that's it. If you want thread safety, you should wrap a <code>Bag</code>
37:   *  in a thread-safety wrapper of your own devising. You can use the
38:   *  methods of the <code>Collection</code> class for this purpose if
39:   *  you like, but only if you do not call any methods of <code>Bag</code>
40:   *  that are not defined in the <code>Collection</code> interface.
41:   *
42:   *  Though the <code>Bag</code> is not thread safe with respect to
43:   *  the outside world, it is thread safe with respect to Swing. That is,
44:   *  it's okay for the <code>Bag</code> to be modified, even if a proxy
45:   *  is exposing a UI.
46:   *  
47:   *  <p>Modifying the wrapped collection directly, rather than by calling a
48:   *  Bag method, is dangerous. If you need to modify an element, remove it,
49:   *  change it, then put it back.
50:   *  
51:   **/
52:  
53:  public class Bag implements User_interface, Collection
54:  {
55:      // A bug in the compiler (version 1.2.2 and all prior versions)
56:      // erroneously prints the error message "xxx may not have been
57:      // initialized," when the following are made final.  This bug
58:      // is somehow related to the presence of inner classes, but I'd
59:      // rather have the inner classes than the immutable fields.
60:  
61:   private /*final*/ Collection contents;
62:   private /*final*/ Comparator sort_strategy;
63:   private /*final*/ String     name;
64:   private /*final*/ Vector     proxies = new Vector();
65:   private           Object     top     = null;
66:  
67:      /** Create a bag that uses the indicated Collection to represent
68:       *  the indicated attribute.
69:       */
70:  
71:   public Bag( Collection contents, String name 
)
72:      {   this.contents       = contents;
73:          this.name           = name;
74:          this.sort_strategy  = null;
75:      }
76:  
77:      /** Create a <code>Bag</code> that wraps the <code>Collection</code>
78:       *  passed in as an argument. When the items in the collection are
79:       *  displayed, they are first extracted to an array, which is then
80:       *  sorted using the supplied <code>Comparator</code>. (There's
81:       *  no point in doing this if the underlying Collection is
82:       *  already sorted, but it's handy for <code>HashSet</code> objects
83:       *  and <code>LinkedList</code>.)
84:       */
85:  
86:   public Bag( Collection contents,
String name, Comparator sort_strategy )
87:      {   this.contents       = contents;
88:          this.name           = name;
89:          this.sort_strategy  = sort_strategy;
90:      }
91:  
92:      /*==================================================================*/
93:      /*              Pass-through (to Collection) methods.               */
94:      /*==================================================================*/
95:  
96:   public boolean add   (Object o)       { boolean result = contents.add(o);
97:                                              changed(1);
98:                                              return result;
99:                                            }
100:   public boolean addAll(Collection c)   { boolean result
= contents.addAll(c);    
101:                                              changed(1);
102:                                              return result;
103:                                            }
104:   public boolean remove(Object o)       { boolean result = contents.remove(o);     
105:                                              changed(-1);
106:                                              return result;
107:                                            }
108:   public void    clear()                { contents.clear();   
109:                                              changed(-1);
110:                                            }
111:   public boolean removeAll(Collection c){ boolean
result = contents.removeAll(c); 
112:                                              changed(-1);
113:                                              return result;
114:                                            }
115:   public boolean retainAll(Collection c){ boolean
result = contents.retainAll(c); 
116:                                              changed(-1);
117:                                              return result;
118:                                            }
119:  
120:   public int      size        ()              {return contents.size();        }
121:   public boolean  isEmpty     ()              {return contents.isEmpty();
}
122:   public boolean  contains    (Object o)      {return contents.contains(o);
}
123:   public Iterator iterator    ()              {return contents.iterator();
}
124:   public Object[] toArray     ()              {return contents.toArray();
}
125:   public Object[] toArray     (Object a[])    {return contents.toArray(a);
}
126:   public boolean  containsAll (Collection c)
{return contents.containsAll(c);}
127:   public boolean  equals      (Object o)      {return contents.equals(o);
}
128:   public int      hashCode    ()              {return contents.hashCode();
}
129:  
130:      /*==================================================================*/
131:      /*                  "Top" item management                           */
132:      /*==================================================================*/
133:  
134:      /** Return the current "top" element.
135:       */
136:  
137:   public Object top()
138:      {   return top;
139:      }
140:  
141:   private ActionListener subscription_list = null;
142:  
143:      /** Add a listener that's notified when the designated "top" element
144:       *  changes, either through a call to {@link #designate_top}
145:       *  or by a user-mandated change made via a visual proxy.
146:       */
147:  
148:   public final void addActionListener(
ActionListener subscriber )
149:      {   subscription_list =
150:              AWTEventMulticaster.add( subscription_list, subscriber );
151:      }
152:  
153:      /** Remove a listener added by {@link #addActionListener}.
154:       */
155:  
156:   public final void
removeActionListener( ActionListener subscriber )
157:      {   subscription_list =
158:              AWTEventMulticaster.remove( subscription_list, subscriber );
159:      }
160:  
161:      /** Designate a new "top" element and notify any listeners that
162:       *  the top item has changed.. Typically, this will be done
163:       *  by the user picking an element in a proxy, but you can
164:       *  do it manually by calling this method.
165:       *  @param top the new "top" element. This element must be
166:       *          an element of the encapsulated Collection, either added
167:       *          directly or added via the <code>Bag</code> version
168:       *          of {@link add()}.
169:       *  @throws IllegalArgumentException if the argument doesn't
170:       *          identify an item already in the bag.
171:       */
172:  
173:   public void designate_top(final Object top)
174:      {
175:          if( !contents.contains( top ) )
176:              throw new IllegalArgumentException(
177:                          "Designated top item not in Bag (" + top + ")" );
178:          this.top = top;
179:  
180:          if( subscription_list != null )
181:              subscription_list.actionPerformed
182:              (   new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
183:                                                          top.toString())
184:              );
185:  
186:          Object[] copy;
187:          synchronized(proxies){ copy = proxies.toArray(); }
188:  
189:       for( int i = 0; i < copy.length; ++i )
190:          {   final Proxy proxy = (Proxy)copy[i];
191:              SwingUtilities.invokeLater
192:              (   new Runnable()
193:               {   public void run()
194:                      {   proxy.new_selection(top);
195:                          proxy.repaint();
196:                      }
197:                  }
198:              );
199:          }
200:      }
201:  
202:      /*==================================================================*/
203:      /*                        Visual proxy                              */
204:      /*==================================================================*/
205:  
206:   private void changed( final int direction )
207:      {
208:          Object[] copy;
209:          synchronized(proxies){ copy = proxies.toArray(); }
210:  
211:          for( int i = 0; i < copy.length; ++i )
212:          {   final Proxy proxy = (Proxy)copy[i];
213:              SwingUtilities.invokeLater
214:              (   new Runnable()
215:               {   public void run()
216:                      {   proxy.changed( direction );
217:                          proxy.repaint();
218:                      }
219:                  }
220:              );
221:          }
222:      }
223:  
224:      /** Manufacture a "visual proxy" for the current <code>Bag</code>,
225:       *  suitable for inclusion in a "form". Currently, only one
226:       *  proxy type (<code>"chooser"<code>) is supported.
227:       *  The <code>Bag</code>
228:       *  displays itself as a JList, JComboBox, or JButton, depending
229:       *  on the amount of screen real estate available to it. In the
230:       *  case of a button, the button label is the <code>attribute</code>
231:       *  argument. Note that you can add an Ancestor listener to the
232:       *  returned proxy to find out when the window that contains
233:       *  the proxy shuts down. The most-recently selected item
234:       *  can be fetched, at that point, by calling {@link #selected_item()}
235:       *
236:       *  @see Form
237:       *  @see User_interface
238:       */
239:  
240:   public JComponent visual_proxy( String
attribute_name, boolean is_read_only )
241:      {   
242:          if( !is_read_only )
243:              return null;
244:      
245:          // Set things up so that the proxy will be in the "proxies"
246:          // list whenever it's visible. It's essential that it be removed
247:          // from the list when invisible; otherwise, the reference in
248:          // the list will keep the <code>Proxy</code> object from being garbage
249:          // collected after the containing window shuts down.
250:  
251:          final Proxy proxy = new Proxy( name );
252:          proxy.addAncestorListener
253:          (   new AncestorAdapter()
254:           {   public void ancestorRemoved(AncestorEvent
event)    
255:                  {   synchronized( proxies )
256:                      {   proxies.remove( proxy );
257:                      }
258:                  }
259:               public void ancestorAdded(AncestorEvent
event)
260:                  {   synchronized( proxies )
261:                      {   proxies.add( proxy );
262:                      }
263:                  }
264:              }
265:          );
266:          return proxy;
267:      }
268:  
269:      /** The actual visual-proxy class is a JPanel that contains either
270:       *  a button, combo box, or list box, depending on its size. An
271:       *  instance of this class is returned by the <code>visual_proxy()</code>
272:       *  request.
273:       */
274:  
275:   public class Proxy extends JPanel 
276:      {
277:       private int         selected_size = 10;
278:       private String      attribute_name;
279:       private JComboBox   drop_down;
280:       private JList       list;
281:       private JButton     button;
282:       private JComponent  current_ui;
283:  
284:       private Model    state  = new Model();
285:       private Renderer artist = new Renderer();
286:  
287:          /** A private constructor, creates a proxy for the current
288:           *  Bag. Since this constructor is private, you may not issue
289:           *  a <code>new Bag("xxx")</code> request. Get a proxy by
290:           *  calling the Bag's {@link Bag#visual_proxy} method.
291:           *
292:           *  @see Bag#visual_proxy.
293:           */
294:  
295:       private Proxy( final String attribute_name )
296:          {
297:              drop_down   = new JComboBox  ( state );
298:              list        = new JList      ( state );
299:              button      = new JButton    ( attribute_name );
300:  
301:              drop_down.setRenderer(artist);
302:              drop_down.setSelectedIndex( 0 );
303:  
304:              // Set up the list. It turns out that the model, though
305:              // sufficient for controlling the appearance of the drop-
306:              // down, is not sufficient to control the list, so set
307:              // up a listener that sets modifies the state of the
308:              // current Bag in response to a selection.
309:  
310:              list.setCellRenderer (artist);
311:              list.addListSelectionListener
312:              (   new ListSelectionListener()
313:               {
public void valueChanged( ListSelectionEvent event )
314:                      {   if( !event.getValueIsAdjusting() )
315:                              designate_top(list.getSelectedValue());
316:                      }
317:                  }
318:              );
319:  
320:              // In the case of the button, set up a listener to throw up
321:              // the selection box (a small frame containing the JList)
322:              // when the button is pressed. Also arrange for the box
323:              // to pop up over the button, rather than in the upper-left
324:              // corner of the screen. The button is disabled as long as
325:              // the selection box is displayed.
326:  
327:              button.addActionListener
328:              (   new ActionListener()
329:               {   public
void actionPerformed( ActionEvent e )
330:                      {   Window popup = new Popup(attribute_name);
331:                          button.setEnabled(false);
332:                          popup.addWindowListener
333:                          (   new WindowAdapter()
334:                           {
public void windowClosing(WindowEvent e)
335:                                  {   button.setEnabled(true);
336:                                  }
337:                              }
338:                          );
339:                          popup.setLocation( button.getLocationOnScreen() );
340:                          popup.show();
341:                      }
342:                  }
343:              );
344:  
345:              // Arrange for the UI to change it's appearance, if necessary,
346:              // when the size changes.
347:  
348:           this.addComponentListener
349:              (   new ComponentAdapter()
350:               {
public void componentResized(ComponentEvent e)
351:                      {   install_ui();
352:                      }
353:                  }
354:              );
355:  
356:              this.attribute_name = attribute_name;
357:              setLayout( new BorderLayout() );
358:          }
359:  
360:          /** Examines the size of the <code>Proxy</code> object and
361:           *  installs the button, drop-down, or list as appropriate. 
362:           *  does nothing if the correct widget is already displayed.
363:           */
364:  
365:       private final void install_ui()
366:          {
367:              Rectangle bounds         = getBounds();
368:              Dimension drop_down_size = drop_down.getPreferredSize();
369:  
370:              boolean use_list   = bounds.height > (drop_down_size.height *3 )
371:                                && bounds.width  > (drop_down_size.width  +10);
372:              boolean use_button = bounds.width  < drop_down_size.width 
373:                                || bounds.height < drop_down_size.height;
374:              boolean use_drop   = !use_list && !use_button;
375:  
376:              if( use_list    && current_ui == list       )   return;
377:              if( use_button  && current_ui == button     )   return;
378:              if( use_drop    && current_ui == drop_down  )   return;
379:  
380:              if( current_ui != null )
381:                  current_ui.setVisible( false );
382:              this.removeAll();
383:  
384:              if( use_button )
385:                  this.add( current_ui = button, BorderLayout.CENTER );
386:              else if( use_list )
387:                  this.add( new JScrollPane(current_ui = list), BorderLayout.CENTER );
388:              else
389:              {   if( current_ui == null )
390:                      drop_down.setSelectedIndex(0);
391:                  this.add( current_ui = drop_down, BorderLayout.NORTH );
392:              }
393:  
394:              current_ui.setVisible( true );
395:              
396:              // Make the newly added component visible, note that
397:              // repaint(), invalidate(), and doLayout() do not work
398:              // for this purpose (probably a bug).
399:  
400:              this.setVisible( false );
401:              this.setVisible( true );
402:          }
403:  
404:          /** Called when the state of the underlying <code>Collection</code>
405:           *  is changed by calling <code>add()</code>, <code>remove()</code>,
406:           *  etc.
407:           *  @param direction    <o if the collection has gotten smaller,<br>
408:           *                      >0 if it's gotten larger.<br>
409:           *                      =0 if it hasn't changed size, but needs
410:           *                          to be refreshed.
411:           */
412:  
413:       public void changed(int direction)
414:          {   int selected_index = list.getSelectedIndex();
415:  
416:              if( selected_index < 0 )
417:                  selected_index = 0;
418:  
419:              if( selected_index >= contents.size() )
420:                  selected_index = contents.size()-1;
421:  
422:              state.changed(direction);
423:  
424:              list.setSelectedIndex       (selected_index);
425:              list.ensureIndexIsVisible   (selected_index);
426:  
427:              drop_down.setSelectedIndex  (selected_index);
428:          }
429:  
430:          /** Called when the user picks an item from the current UI.
431:           *  Makes sure that the drop-down and the list stay in synch
432:           *  with each other.
433:           */
434:  
435:       public void new_selection( Object
selected )
436:          {   if( list.getSelectedValue()  != selected )
437:              {   list.setSelectedValue    ( selected, true );
438:                  list.ensureIndexIsVisible( list.getSelectedIndex() );
439:              }
440:              if( drop_down.getSelectedItem() != selected )
441:              {   drop_down.setSelectedItem( selected );
442:              }
443:          }
444:  
445:          /** Overrides the base-class method, so must be public. Do not
446:           *  call this method. Installs the ui when the proxy is
447:           *  displayed the first time.
448:           */
449:  
450:       public void addNotify()
451:          {   super.addNotify();
452:              install_ui();
453:          }
454:  
455:          /*******************************************************************
456:           * The "model" class, holds the state of the font-name combo box.
457:           * The list of possible fonts is stored here, as is the currently-selected
458:           * font.
459:           */
460:  
461:       private final class Model   extends     AbstractListModel
462:                                      implements  ComboBoxModel
463:          {
464:           public Object getSelectedItem(
){ return top();           } 
465:           public void
setSelectedItem(Object o ){ designate_top(o);       }
466:  
467:           public int    getSize        (        
){ return contents.size(); }
468:           public Object getElementAt
(int index)
469:              {   Object[] items = contents.toArray();
470:                  if( sort_strategy != null )
471:                  {   Arrays.sort( items, sort_strategy );
472:                  }
473:                  return (0 <= index && index < items.length) ? items[index]: null;
474:              }
475:  
476:              /** Call this method if the  collection changes in some way.
477:               *  @param direction should be a negative number if the
478:               *              collection got smaller. A positive number
479:               *              if it got larger, 0 if it didn't change
480:               *              size, but the UI needs redrawing anyway.
481:               */
482:  
483:           public void changed( int direction
)
484:              {   
485:                  if( direction < 0 )
486:                      fireIntervalAdded  (this, 0, contents.size()-1);
487:                  else if( direction > 0 )
488:                      fireIntervalRemoved(this, 0, contents.size()-1);
489:                  else
490:                      fireContentsChanged(this, 0, contents.size()-1);
491:              }
492:          }
493:  
494:          /*******************************************************************
495:           * The JList and JComboBox use the Renderer to draw its cells.
496:           */
497:  
498:       private final class Renderer implements ListCellRenderer     
499:          {   
500:           private JPanel selected_wrapper
= new JPanel();
501:  
502:           public Renderer()
503:              {   selected_wrapper.setLayout( new BorderLayout() );
504:                  selected_wrapper.setBorder(
505:                              BorderFactory.createLineBorder(Color.red) );
506:              }
507:  
508:              // Return a Component whose paint method is used to draw
509:              // the cell. Note that that's the only thing that the
510:              // Component is used for. The component itself it not put
511:              // into the cell.
512:  
513:     
public Component getListCellRendererComponent(
514:                                          JList list, Object value, int index, 
515:                                          boolean isSelected, boolean cellHasFocus) 
516:              {
517:                  JComponent item;
518:  
519:                  if(value == null )
520:                      value = "????";
521:  
522:               if( value instanceof User_interface )
523:                  {   item = ((User_interface)value).visual_proxy(null,true);
524:                  }
525:                  else
526:                  {   item = new JLabel( value.toString() )
527:                   {   public
Dimension getPreferredSize()
528:                          {   Dimension d = super.getPreferredSize();
529:                              d.height += 8;
530:                              d.width  += Math.min( d.width+8, 100 );
531:                              return d;
532:                          }
533:                      };
534:               }
535:  
536:                  if( isSelected )
537:                  {   
538:                      selected_wrapper.removeAll();
539:                      selected_wrapper.add( item, BorderLayout.CENTER );
540:                      item = selected_wrapper;
541:                  }
542:  
543:                  return item;
544:              }
545:          }
546:  
547:          /***************************************************************
548:           * The "selection frame" that pop's up when the button UI is
549:           * pressed. The (non-modal) frame contains a panel with an
550:           * etched border, which in turn, holds the same JList object
551:           * that appears when the Proxy has enough room to display it.
552:           */
553:  
554:       private final class Popup extends JFrame
555:          {   
556:           public Popup( String attribute_name
)
557:              {   JPanel interior = new JPanel();
558:                  interior.setBorder
559:                          (   BorderFactory.createTitledBorder
560:                              (   new EtchedBorder(Color.white,Color.gray), 
561:                                  attribute_name
562:                              )
563:                          );
564:  
565:                  interior.setLayout( new BorderLayout() );
566:                  interior.add( new JScrollPane(list), BorderLayout.CENTER );
567:  
568:                  list.setVisible( true );
569:                  setContentPane( interior );
570:                  pack();
571:              }
572:          }
573:      }
574:  
575:      /*==================================================================*/
576:      /*                              TEST                                */
577:      /*==================================================================*/
578:  
579:      /** This small test class demonstrates how to use a Bag. It creates a
580:       *  <code>Bag</code>, puts a few items in it, displays two UIs, then
581:       *  allows you to add elements to the collection. You can resize the
582:       *  windows to watch the display change from on appearance to another,
583:       *  and when you type lines into the console window, the new lines
584:       *  appear in both of the UI windows. Also notice that the proxies
585:       *  stay in synch with each other with respect to selection as
586:       *  well (If you don't want this last behavior, wrap the same
587:       *  <code>Collection</code> object into two distinct <code>Bag</code>
588:       *  objects.
589:       */
590:  
591:   public static class Test
592:      {
593:       public void create_ui( Collection
aggregate )
594:          {
595:              JFrame frame = new JFrame();
596:  
597:              // Get the visual proxy and shove it into the Frame
598:  
599:              User_interface displayable = (User_interface)aggregate;
600:              frame.getContentPane().add(
601:                          displayable.visual_proxy("Attribute", true) );
602:  
603:              // Set up a window-closing handler and pop the frame up.
604:  
605:              frame.addWindowListener
606:                  (   new WindowAdapter()
607:                   {   public
void windowClosing( WindowEvent e )
608:                          {   System.exit(0);
609:                          }
610:                      }
611:                  );
612:              frame.pack();
613:              frame.show();
614:          }
615:  
616:       public static void main( String[] args ) throws Exception
617:          {   
618:              Collection aggregate = new Bag( new LinkedList(), "outer" );
619:              aggregate.add("A");
620:              aggregate.add("B");
621:              aggregate.add("C");
622:              aggregate.add("D");
623:  
624:              // You need to treat it as a Bag (as compared to a generic
625:              // Collection) to install an ActionListener, thus the cast.
626:              // It has to be final because it's referenced by the inner-
627:              // class object. Note that the listener will report all the
628:              // selections associated with displaying the initial UI.
629:              // There will be two such notifications (one for the list
630:              // and one for the drop-down) for each of the three proxies.
631:              // If you don't want this behavior, install the listener
632:              // after the visual proxy has been displayed.
633:  
634:           final Bag the_bag = (Bag) aggregate;
635:              the_bag.addActionListener
636:              (   new ActionListener()
637:               {   public
void actionPerformed( ActionEvent e )
638:                      {   System.out.println( "Selected " + the_bag.top() );
639:                      }
640:                  }
641:              );
642:  
643:              create_ui( aggregate );
644:              create_ui( aggregate );
645:              create_ui( aggregate );
646:  
647:              // Transfer all lines typed on the console to the collection.
648:  
649:              String s;
650:              while( (s = com.holub.tools.Std.in().readLine()) != null )
651:              {   com.holub.tools.Std.out().println( "->" + s );
652:                  aggregate.add( s );
653:              }
654:          }
655:      }
656:      //END_TEST
657:  }
                                                
                                    

Whew!

Though all this code seems complicated (because it is complicated), you don't write code like this very often. You use it a lot, however, and this class is pretty simple to use. Put a Collection into it, then use it like a Collection. Call visual_proxy() when you need to display a UI for the collection. That's it. With only a few wrappers like Bag, you can easily create visual proxies for many of the attributes of most abstraction-level classes.

Hopefully, I've also demonstrated the strengths of this architecture vis-a-vis eliminating the tight-coupling relationships inherent in many of the architectures that I've discussed in previous articles. A Bag provides a general, very loosely coupled mechanism for displaying object aggregations without knowing anything about what classes those objects instantiate.

Next month, I'll continue on the UI theme by discussing how a proxy can interact with the user via the application's main menu. In particular, I'll provide an implementation of a menu site class that defines the negotiation necessary for a proxy to get a seat at the menu bar. The proxy can then use this facility to let a user pick a particular look and feel by communicating directly to the proxy via a menu, rather than through the surrounding frame.

Allen Holub has been working in the computer industry since 1979. He is widely published in magazines (Dr. Dobb's Journal, Programmers Journal, Byte, MSJ, among others). He has seven books to his credit, and is currently working on an eighth that will present the complete sources for a Java compiler written in Java. After eight years as a C++ programmer, Allen abandoned C++ for Java in early 1996. He now looks at C++ as a bad dream, the memory of which is mercifully fading. He's been teaching programming (first C, then C++ and MFC, now OO-Design and Java) both on his own and for the University of California Berkeley Extension since 1982. Allen offers both public classes and in-house training in Java and object-oriented design topics. He also does object-oriented design consulting and contract Java programming. Get information, and contact Allen, via his Web site http://www.holub.com.

Learn more about this topic

  • A usual, the code to this month's article is available in the "Articles" section of my Web site. An index of all my previous JavaWorld articles (including the first two parts of the current series) can be found there as well. http://www.holub.com
  • Design PatternsElements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides -- the "Gang of Four" (Addison Wesley, 1994) http://www1.fatbrain.com/asp/bookinfo/bookinfo.asp?theisbn=0201633612
1 2 3 Page 3
Page 3 of 3