Decorate your Java code

A look at the Decorator design pattern

Being somewhat of a closet Luddite, I only recently purchased my first cell phone. My phone is faceplate-ready -- in other words, I can snap on a stylish faceplate. As far as I can tell, faceplates are the only dynamically changeable aspect of my cell phone. For example, I cannot snap on antennas or speakers; those are set statically at the factory.

Software decorators, like cell phone faceplates, encapsulate some functionality that you can snap onto an existing object; for example, here's a code fragment that snaps sorting onto an existing Swing table model:

TableSortDecorator sortDecorator = new TableSortDecorator(table.getModel());
table.setModel(sortDecorator);

The preceding code swaps out a table's model for a decorator. After the swap, whenever the table accesses its model, it unknowingly accesses the sort decorator. The decorator adds sorting capabilities to the model it decorates, and delegates other functionality to the real model.

Like cell phones, software heavily reliant on inheritance includes a high percentage of statically defined objects. Just as a cell phone's antenna or speaker is statically defined at the factory, base classes and their extensions are statically defined at compile time. Because inheritance is static, it does not allow you to swap out an aspect of an object's behavior at runtime (such as the table model decorator in the code fragment above). Because you can use decorators much like Legos to snap together an object with desired behaviors at runtime, the Decorator design pattern proves far more flexible than inheritance.

In this article, I first introduce the Decorator pattern, starting with an example that shows how to use Java's I/O decorators. I then outline the Decorator pattern's statics and dynamics with UML class and sequence diagrams, respectively. I conclude with an example decorator that, as alluded to above, adds sorting to Swing tables.

Note: In the first installment of this column -- "Amaze Your Developer Friends with Design Patterns" -- I introduced Decorator, among other patterns. You may wish to read that introduction before proceeding with this more detailed look.

The Decorator pattern demystified

Decorator: Attaches responsibilities to objects at runtime. Decorators prove more flexible than inheritance, which attaches responsibilities to classes at compile time.

In "Amaze Your Developer Friends with Design Patterns," I described input streams and the Decorator pattern. Example 1 from that article demonstrates instantiating a line number reader:

Example 1. Instantiating I/O decorators

1. FileReader       frdr = new FileReader(filename);
2. LineNumberReader lrdr = new LineNumberReader(frdr);

The preceding code creates a reader -- lrdr -- that reads from a file and tracks line numbers. Line 1 creates a file reader (frdr), and line 2 adds line-number tracking.

At runtime, decorators forward method calls to the objects they decorate. For example, in the code above, the line number reader, lrdr, forwards method calls to the file reader, frdr. Decorators add functionality either before or after forwarding to the object they decorate; for example, our line number reader tracks the current line number as it reads from an input stream.

Alternatively, of course, you could write Example 1 like this:

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

Both methods outlined above adhere to the same construction idiom: wrapping objects recursively -- a hallmark of the Decorator design pattern.

The code in Example 2 below shows how to instantiate and use a line number reader. The example reads lines of text from a file and prints each line with its line number:

Example 2. Using I/O decorators

try {
   LineNumberReader lrdr = new LineNumberReader(new FileReader(filename));
   for(String line; (line = lrdr.readLine()) != null;)rticle.txt {
      System.out.print(lrdr.getLineNumber() + ":\t" + line);
   }
}
catch(java.io.FileNotFoundException fnfx) {
   fnfx.printStackTrace();
}
catch(java.io.IOException iox) {
   iox.printStackTrace();
} 

Decorators represent a powerful alternative to inheritance. Whereas inheritance lets you add functionality to classes at compile time, decorators let you add functionality to objects at runtime.

Decorator statics and dynamics

Mechanical engineers study statics and dynamics. Statics analyze forces on objects that don't move very much -- bridges or buildings, for instance. Dynamics analyze forces on moving objects, typically in machines. The centrifugal forces on jet engine blades or clock pendulums are good examples.

Statics and dynamics have direct counterparts in the realm of software design patterns. Design pattern statics analyze class relationships specified at compile time, whereas design pattern dynamics analyze the runtime sequence of events in which objects participate. In this section, I show design pattern statics with UML class diagrams and design pattern dynamics with UML sequence diagrams.

Throughout the Java Design Pattern column, I will discuss the static and dynamic aspects of each design pattern we explore. Let's begin by examining the statics and dynamics of the pattern at hand -- Decorator.

Decorator statics

Decorators decorate an object by enhancing (or in some cases restricting) its functionality. Those objects are referred to as decorated. Figure 1 shows the static relationship between decorators and the decorated.

Figure 1. Decorator class diagram. Click on thumbnail to view full-size image.

Decorators extend the decorated class (or implement the decorated interface), which lets decorators masquerade as the objects they decorate. They also maintain a reference to a Decorated instance. That instance is the object that the decorator decorates. As an example of how the classes in the Decorator pattern relate, Figure 2 depicts the static relationships among four decorators from the java.io package:

  • BufferedReader
  • LineNumberReader
  • FilterReader
  • PushbackReader
Figure 2. I/O decorators

BufferedReader and FilterReader are decorators just like the one shown in Figure 1. Both classes extend the abstract Reader class, and both forward method calls to an enclosed Reader. Because they extend BufferedReader and FilterReader, respectively, LineNumberReader and PushbackReader are also decorators.

Decorator dynamics

At runtime, decorators forward method calls to the objects they decorate, as shown in Figure 3.

Figure 3. Decorator dynamics. Click on thumbnail to view full-size image.

Developers often refer to Decorators as wrappers because they wrap method calls to decorated objects. Figure 3 clearly depicts such wrapping. Figure 4 shows the dynamics of the code listed in Example 2.

Figure 4. I/O decorator dynamics

Now that we have a high-level understanding of Decorator's statics and dynamics, let's use an example to examine the implementation of the Decorator pattern.

Sort and filter decorators for Swing tables

The Decorator pattern excels at attaching functionality, such as sorting and filtering, to Swing tables. In fact, the Decorator pattern is flexible enough that we can attach functionality to any Swing table at runtime. Before we can discuss how decorators enhance Swing tables in detail, we must have a basic understanding of Swing tables; so, let's take a short detour.

Swing tables

Swing components are implemented with the Model-View-Controller (MVC) design pattern. Each component consists of a model that maintains data, views that display the data, and controllers that react to events. For example, Swing tables, instances of JTable, are commonly created with an explicit model. Example 3 lists an application that does just that. (Try to picture the table in Example 3 before viewing it in Figure 5.)

Example 3. Create a Swing table

import javax.swing.*;
import javax.swing.table.*;
public class Test extends JFrame {
   public static void main(String args[]) {
      Test frame = new Test();
      frame.setTitle("Tables and Models");
      frame.setBounds(300, 300, 450, 300);
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.show();
   }
   public Test() {
      TableModel model = new TestModel();   
      getContentPane().add(new JScrollPane(new JTable(model)));
   }
   private static class TestModel extends AbstractTableModel {
      final int rows = 100, cols = 10;
      public int    getRowCount()    { return rows; }
      public int    getColumnCount() { return cols; }
      public Object getValueAt(int row, int col) {
         return "(" + row + "," + col + ")";
      }
   }
}

The application creates a table with a model. That table is placed in a scrollpane and added to the frame's content pane. Figure 5 shows the application listed in Example 3.

Figure 5. A simple Swing table

The application shown above creates a table model with 100 rows and 10 columns. When asked for a cell value, the model creates a string that represents the cell's row and column. The application uses that model to construct a Swing table (an instance of JTable). Table models produce a table's data, in addition to metadata such as the number of rows and columns.

It's important to understand that Swing tables possess models that maintain a table's data. Table models, as evidenced in Example 3, do not have to actually store any data -- they must produce a value for a given row and column.

A sort decorator

With a basic understanding of the Decorator pattern and Swing tables, we can now implement a table sort decorator. First, we'll see an application that uses the decorator, followed by a discussion of the decorator's implementation.

The application shown in Figure 6 contains a table with two columns: one for items and another for price. You can sort columns by clicking on column headers. When the application starts, nothing is sorted, as evidenced by the status bar in the bottom of the left picture in Figure 6. I took the middle picture in Figure 6 after I clicked the Item column header. I took the the right picture after I clicked the Price/Lb. column header.

Figure 6. A sorting decorator. Click on thumbnail to view full-size image.

The application shown in Figure 6 replaces the table's model with a sort decorator. That application is partially listed in Example 4. (You can download the full source code from the Resources section below.)

Example 4. Sorting a Swing table

import java.awt.*;
import java.awt.event.*;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
public class Test extends JFrame {
   public static void main(String args[]) {
      SwingApp.launch(new Test(), "A Sort Decorator", 
                                   300, 300, 200, 250);
   }
   public Test() {
      // Create the decorator that will decorate the table's 
      // original model. The reference must be final because it's
      // accessed by an inner class below. 
      final TableSortDecorator decorator = 
                new TableSortDecorator(table.getModel());
      // Set the table's model to the decorator. Because the
      // decorator implements TableModel, the table will never
      // know the difference between the decorator and it's
      // original model.
      table.setModel(decorator);
      ...
      // Obtain a reference to the table's header
      JTableHeader header = (JTableHeader)table.getTableHeader();
      // React to mouse clicks in the table header by calling 
      // the decorator's sort method.
      header.addMouseListener(new MouseAdapter() {
         public void mouseClicked(MouseEvent e) {
            TableColumnModel tcm = table.getColumnModel();
            int vc = tcm.getColumnIndexAtX(e.getX());
            int mc = table.convertColumnIndexToModel(vc);
            // Perform the sort
            decorator.sort(mc);
            // Update the status area
            SwingApp.showStatus(headers[mc] + " sorted");
         }
      });
   }
   ...
}

In the preceding code, a sort decorator decorates the table's original model. When you click on a column header, the application calls the decorator's sort() method. The interesting code in this example is not the application, but the sort decorator. Before looking at the source to TableSortDecorator, let's look at its class diagram, shown in Figure 7.

Figure 7. Sorting decorator class diagram

Since instances of TableSortDecorator decorate table models, TableSortDecorator implements the TableModel interface. TableSortDecorator also maintains a reference to the real model that it decorates. That reference can point to any table model. Example 5 lists the TableSortDecorator class:

Example 5. A table sort decorator

Related:
1 2 3 Page 1
Page 1 of 3