Amaze your developer friends with design patterns

Java Design Patterns column kicks off with a look at three important design patterns

1 2 Page 2
Page 2 of 2

Example 2. Use the Composite pattern

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Panel;
import java.awt.Label;
import java.awt.TextField;
public class Test extends Applet {
   public void init() {
      Panel        center = new Panel();
      WorkPanel workPanel = new WorkPanel(center);
      workPanel.addButton("Ok");
      workPanel.addButton("Cancel");
      center.add(new Label("Name:"));
      center.add(new TextField(25));
      setLayout(new BorderLayout());
      add(workPanel);
   }
}
class WorkPanel extends Panel {
   private Panel buttonPanel = new Panel();
   public WorkPanel(Panel centerPanel) {
      setLayout(new BorderLayout());
      add(centerPanel, "Center");
      add(buttonPanel, "South");
   }
   public void addButton(String label) {
      buttonPanel.add(new Button(label));
   }
}

The applet listed in Example 2 contains a panel -- an instance of WorkPanel -- that contains two other panels, as shown in Figure 3.

Figure 3. Nest AWT containers

Because the AWT implements components and containers using the Composite pattern, Java applets and applications, like the one listed in Example 2, can easily nest components and containers in a tree structure. Additionally, components and containers can be treated uniformly.

The Decorator pattern

The java.io package provides, among other things, a set of classes for reading input streams. Known as readers, each of those classes has a name that follows the pattern: xxxReader.

Readers provide specialized functionality; for example, one reader reads from a file, another tracks line numbers, and yet another pushes characters back on the input stream. In all, eight different readers exist to read input streams.

It's often necessary to combine the capabilities offered by java.io readers; for example, you might want to read from a file, keep track of line numbers, and push certain characters back on the input stream, all at the same time. The java.io package's designers could have used inheritance to provide a wide array of such reader combinations; for example, a FileReader class could have a LineNumberFileReader subclass, which could in turn have a PushBackLineNumberFileReader subclass. But using inheritance to compose the most widely used combinations of reader functionality would result in a veritable explosion of classes. So how did the java.io package's designers create a design that allows you to combine reader functionality in any way you desire with only 10 reader classes? As you might guess, they used the Decorator pattern.

Instead of using inheritance to add functionality to classes at compile time, the Decorator pattern lets you add functionality to individual objects at runtime. That is accomplished by enclosing an object in another object. The enclosing object forwards method calls to the enclosed object and typically adds some functionality of its own before or after forwarding. The enclosing object -- known as a decorator -- conforms to the interface of the object it encloses, allowing the decorator to be used as though it were an instance of the object it encloses. That may sound complicated, but using decorators is actually quite simple. For example, the code listed in Example 3 uses a decorator to read and print the contents of a file. The code also prints line numbers and transforms "Tab" characters to three spaces.

Example 3. Use the Decorator pattern

import java.io.FileReader;
import java.io.LineNumberReader;
public class Test {
   public static void main(String args[]) {
      if(args.length < 1) {
         System.err.println("Usage: " + "java Test filename");
         System.exit(1);
      }
      new Test(args[0]);
   }
   public Test(String filename) {
      try {
         FileReader       frdr = new FileReader(filename);
         LineNumberReader lrdr = new LineNumberReader(frdr);
         for(String line; (line = lrdr.readLine()) != null;) {
            System.out.print(lrdr.getLineNumber() + ":\t");
            printLine(line);
         }
      }
      catch(java.io.FileNotFoundException fnfx) {
         fnfx.printStackTrace();
      }
      catch(java.io.IOException iox) {
         iox.printStackTrace();
      }
   }
   private void printLine(String s) {
      for(int c, i=0; i < s.length(); ++i) {
         c = s.charAt(i);
         if(c == '\t') System.out.print("   ");
         else          System.out.print((char)c);
      }
      System.out.println();
   }
}

If you use the code listed in Example 3 to read itself, you will get output that looks like Example 4.

Example 4. Output from using the application in Example 3 to print itself

1:    import java.io.FileReader;
2:    import java.io.LineNumberReader;
3:        
4:    public class Test {
5:       public static void main(String args[]) {
6:          if(args.length < 1) {
7:             System.err.println("Usage: " + "java Test filename");
8:             System.exit(1);
9:          }
10:         new Test(args[0]);
11:      }
12:      public Test(String filename) {
13:         try {
14:            FileReader       frdr = new FileReader(filename);
15:            LineNumberReader lrdr = new LineNumberReader(frdr);
16:        
17:            for(String line; (line = lrdr.readLine()) != null;) {
18:               System.out.print(lrdr.getLineNumber() + ":\t");
19:               printLine(line);
20:            }
21:         }
22:         catch(java.io.FileNotFoundException fnfx) {
23:            fnfx.printStackTrace();
24:         }
25:         catch(java.io.IOException iox) {
26:            iox.printStackTrace();
27:         }
28:      }
29:      private void printLine(String s) {
30:         for(int c, i=0; i < s.length(); ++i) {
31:            c = s.charAt(i);
32:        
33:            if(c == '\t') System.out.print("   ");
34:            else          System.out.print((char)c);
35:         }
36:         System.out.println();
37:      }
38:   }

Notice how I've constructed the line reader used in Example 3:

FileReader       frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);

The LineNumberReader decorator encloses another reader; in this case, the enclosed reader is an instance of FileReader. The line number reader forwards method calls, such as read(), to its enclosed reader and tracks line numbers, which can be accessed with LineNumberReader.getLineNumber(). Because LineNumberReader is a decorator, you can easily track line numbers for any type of reader.

More to come

In this article I provided an overview of design patterns, but in the process I may have created as many questions as I've answered. For example, although I demonstrated how to use three popular design patterns, I did not show you how to implement those patterns. In subsequent articles, I will discuss many of the design patterns from the GOF book in detail, including the best uses for those patterns, and how they are used and implemented.

Homework

If you look at the reader classes in java.io, you will find another decorator: BufferedReader. That class buffers reads, making it more efficient than an unbuffered reader. In light of this new discovery, you might decide to make Example 3 more efficient, like this:

FileReader       frdr = new FileReader(filename);
BufferedReader   brdr = new BufferedReader(frdr); // "mix in" a buffered reader
LineNumberReader lrdr = new LineNumberReader(brdr);

Answer the following questions:

  1. Will the preceding code fragment compile?
  2. How can you add two lines of code to the main method to see if the example runs faster?
  3. Will the modified example run faster? Why or why not?
David Geary is the author of Advanced JavaServer Pages (Prentice Hall, 2001; ISBN: 0130307041) and the Graphic Java series (Sun Microsystems Press). David has been developing object-oriented software with numerous object-oriented languages for 17 years. Since the GOF Design Patterns book was published in 1994, David has been an active proponent of design patterns, and has used and implemented design patterns in Smalltalk, C++, and Java. In 1997, David began working full-time as an author and occasional speaker and consultant. David is a member of the expert groups defining the JSP standard custom tag library and JavaServer Faces, and is a contributor to the Apache Struts JSP framework.

Learn more about this topic

1 2 Page 2
Page 2 of 2