![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
Java Design Patterns
SummaryBy David Geary
The Decorator design pattern lets you attach responsibilities to objects at runtime. This pattern proves more flexible than inheritance, which is static. In this latest installment of Java Design Patterns, David Geary explores Decorator with an implementation example. (2,800 words; December 14, 2001)
eing 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 class TableSortDecorator implements
TableModel,TableModelListener{
public
TableSortDecorator(TableModel model)
{
this.realModel = model;
realModel.addTableModelListener(this);
allocate();
}
// The following nine methods are defined by the
TableModel
// interface; all of those methods forward to the
real model.
public void addTableModelListener(TableModelListener
l)
{
realModel.addTableModelListener(l);
}
public Class getColumnClass(int columnIndex)
{
return
realModel.getColumnClass(columnIndex);
}
public
int getColumnCount() {
return
realModel.getColumnCount();
}
public String getColumnName(int columnIndex)
{
return
realModel.getColumnName(columnIndex);
}
public
int getRowCount() {
return
realModel.getRowCount();
}
public
boolean isCellEditable(int rowIndex, int columnIndex)
{
return
realModel.isCellEditable(rowIndex, columnIndex);
}
public void removeTableModelListener(TableModelListener l)
{
realModel.removeTableModelListener(l);
}
public Object getValueAt(int row, int column)
{
return
getRealModel().getValueAt(indexes[row], column);
}
public void setValueAt(Object aValue, int row, int column)
{
getRealModel().setValueAt(aValue,
indexes[row], column);
}
// The getRealModel
method is used by subclasses to
// access the real
model.
protected TableModel getRealModel()
{
return realModel;
}
// tableChanged is defined in TableModelListener,
which
// is implemented by TableSortDecorator.
public void tableChanged(TableModelEvent e)
{
allocate();
}
// The following methods perform the
bubble sort ...
public void sort(int column)
{
int rowCount =
getRowCount();
for(int i=0; i <
rowCount; i++) {
for(int j =
i+1; j < rowCount; j++)
{
if(compare(indexes[i],
indexes[j], column) < 0)
{
swap(i,j);
}
}
}
}
private void swap(int i, int j) {
int tmp
= indexes[i];
indexes[i] =
indexes[j];
indexes[j] =
tmp;
}
private int compare(int i, int j, int
column) {
TableModel realModel =
getRealModel();
Object io =
realModel.getValueAt(i,column);
Object jo
= realModel.getValueAt(j,column);
int
c =
jo.toString().compareTo(io.toString());
return
(c < 0) ? -1 : ((c > 0) ? 1 : 0);
}
private void allocate() {
indexes = new
int[getRowCount()];
for(int i=0; i <
indexes.length; ++i) {
indexes[i] = i;
}
}
private TableModel realModel; // We're decorating this model
private int indexes[];
}
Table sort decorators do not change their real model when they sort; instead, decorators maintain an array of integers representing sorted row positions. When a sort decorator, masquerading as a table model, is asked for a value at a specific row and column, it uses the row value as an index into its array, and returns the corresponding value from the array. In that way, sort decorators overlay sorting onto Swing table models without modifying the original model in any way.
TableSortDecorator
also implements the
TableModelListener
interface and registers itself as a listener
with the real model. When the real model fires a table changed event, the
decorator reallocates its array of sorted index references. That allocation
happens in TableSortDecorator.tableChanged
.
TableSortDecorator
has 11 public methods. Nine of those methods
forward to the real model. Of those nine methods, only two methods --
getValueAt()
and setValueAt()
-- provide any
additional functionality. Such ratios are not unusual among decorators; most
decorators add a small amount of behavior to a decorated object that already
performs several functions.
Refactoring the sort decorator
The sort decorator
discussed above works as advertised by decorating arbitrary table models with
sorting functionality at runtime. This gives you flexibility because the
decorated models do not need to be modified, which means that any table model
can be decorated with sorting capabilities.
But the TableSortDecorator
as implemented above is not designed
for reuse because it implements two responsibilities for which it is not well
suited. The first responsibility is forwarding to the real model. Because other
table model decorators will need almost exactly the same code, that
responsibility is too general, and should be moved up the class hierarchy. The
second responsibility is sorting, which in this example is a bubble sort. That
sorting algorithm is hardcoded into the sort decorator, which means sorting
cannot be changed without rewriting the entire decorator. The sorting algorithm
is too specific, so it should be moved down the hierarchy.
Figure 8 shows the result of refactoring the sort decorator as recommended in the preceding paragraph.
Figure 8. Refactored sorting decorator class diagram. Click on
thumbnail to view full-size image. |
The refactoring split the original TableSortDecorator
into three
different classes:
TableModelDecorator
: Implements
TableModel
by forwarding to the real model
TableSortDecorator
: Extends
TableModelDecorator
and adds an abstract sort
method
that subclasses must implement
TableBubbleSortDecorator
: Extends
TableSortDecorator
and implements the bubble sort By refactoring the sort decorator, we can reuse the code that forwards to the
real table model. Encapsulating that code in TableModelDecorator
lets us easily develop other table model decorator types, such as the filter
decorators -- which are not discussed in this article -- shown in Figure 8.
The abstract TableSortDecorator
class defers the implementation
of its sort()
method to subclasses, making it trivial to implement
sort decorators with a different sorting algorithm.
Example 6 lists the TableModelDecorator
class.
Example 6. TableModelDecorator import javax.swing.table.TableModel;
import
javax.swing.event.TableModelListener;
// TableModelDecorator implements
TableModelListener. That
// listener interface defines one method:
tableChanged(), which
// is called when the table model is changed. That
method is
// not implemented in this abstract class; it's left for
//
subclasses to implement.
public abstract class TableModelDecorator
implements
TableModel, TableModelListener {
public
TableModelDecorator(TableModel model)
{
this.realModel = model;
realModel.addTableModelListener(this);
}
// The following 9 methods are defined by the
TableModel
// interface; all of those methods forward to the
real model.
public void addTableModelListener(TableModelListener
l)
{
realModel.addTableModelListener(l);
}
public Class getColumnClass(int columnIndex)
{
return
realModel.getColumnClass(columnIndex);
}
public
int getColumnCount() {
return
realModel.getColumnCount();
}
public String getColumnName(int columnIndex)
{
return
realModel.getColumnName(columnIndex);
}
public
int getRowCount() {
return
realModel.getRowCount();
}
public
Object getValueAt(int rowIndex, int columnIndex)
{
return realModel.getValueAt(rowIndex,
columnIndex);
}
public boolean
isCellEditable(int rowIndex, int columnIndex)
{
return
realModel.isCellEditable(rowIndex, columnIndex);
}
public void removeTableModelListener(TableModelListener l)
{
realModel.removeTableModelListener(l);
}
public void setValueAt(Object aValue,
int
rowIndex, int columnIndex)
{
realModel.setValueAt(aValue, rowIndex,
columnIndex);
}
// The getRealModel method
is used by subclasses to
// access the real
model.
protected TableModel getRealModel()
{
return realModel;
}
private TableModel realModel; // We're decorating this
model
}
Notice that public TableModelDecorator
methods do nothing but
forward to the real model. That makes them good defaults for other classes to
inherit when they extend TableModelDecorator
. Example 7 lists
TableSortDecorator
.
Example 7. TableSortDecorator import javax.swing.table.TableModel;
public abstract class
TableSortDecorator extends
TableModelDecorator {
// Extensions of TableSortDecorator must
implement the
// abstract sort method, in addition to
tableChanged. The
// latter is required because
TableModelDecorator
// implements the TableModelListener
interface.
abstract public void sort(int
column);
public TableSortDecorator(TableModel realModel)
{
super(realModel);
}
}
The abstract TableSortDecorator
class defines one abstract
method -- sort()
-- that must be implemented by concrete
subclasses. The TableBubbleSortDecorator
, listed in Example 8, is
one such class.
Example 8. TableBubbleSortDecorator import javax.swing.table.TableModel;
import
javax.swing.event.TableModelEvent;
public class TableBubbleSortDecorator
extends TableSortDecorator {
// The lone constructor must be
passed a reference to a
// TableModel. This class decorates that
model with additional
// sorting functionality.
public TableBubbleSortDecorator(TableModel model)
{
super(model);
allocate();
}
// tableChanged is defined in TableModelListener,
which
// is implemented by TableSortDecorator.
public void tableChanged(TableModelEvent e)
{
allocate();
}
// Two TableModel methods are overridden
from
// TableModelDecorator ...
public Object
getValueAt(int row, int column) {
return
getRealModel().getValueAt(indexes[row], column);
}
public void setValueAt(Object aValue, int row, int column)
{
getRealModel().setValueAt(aValue,
indexes[row], column);
}
// The following
methods perform the bubble sort ...
public void sort(int
column) {
int rowCount =
getRowCount();
for(int i=0; i <
rowCount; i++) {
for(int j =
i+1; j < rowCount; j++)
{
if(compare(indexes[i],
indexes[j], column) < 0)
{
swap(i,j);
}
}
}
}
private void swap(int i, int j) {
int tmp
= indexes[i];
indexes[i] =
indexes[j];
indexes[j] =
tmp;
}
private int compare(int i, int j, int
column) {
TableModel realModel =
getRealModel();
Object io =
realModel.getValueAt(i,column);
Object jo
= realModel.getValueAt(j,column);
int
c =
jo.toString().compareTo(io.toString());
return
(c < 0) ? -1 : ((c > 0) ? 1 : 0);
}
private void allocate() {
indexes = new
int[getRowCount()];
for(int i=0; i <
indexes.length; ++i) {
indexes[i] = i;
}
}
private int indexes[];
}
With the refactoring of the original table sort decorator, we now have a hierarchy of table decorators that we can extend at will.
Decorator applicability
In Java, developers typically
use inheritance to add functionality to objects. Base classes implement shared
behavior, and extensions add functionality. For example, instead of the sort and
filter decorators discussed above, you could implement SortModel
,
FilterModel
, and, perhaps, SortFilterModel
classes.
Decorators prove more flexible than inheritance because the relationship between decorators and the objects they decorate can change at runtime, but relationships between base classes and their extensions are fixed at compile time.
Of course, both inheritance and the Decorator pattern have their places; in fact, inheritance is used to implement the Decorator pattern. So when should you use the Decorator pattern? It's useful when:
Homework
For this month's homework,
implement a filter decorator -- extending the TableFilterDecorator
class shown in Figure 8 -- that filters high-priced items in the table shown in
Figure 6.
Homework from last column FileReader frdr = new
FileReader(filename);
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 so:
BufferedReader brdr = new
BufferedReader(frdr); // "mix in" a buffered reader
LineNumberReader lrdr =
new LineNumberReader(brdr);
Here are the questions from last time, followed by their correct answers:
Yes (provided you import java.io.BufferedReader
)
Add: long time =
System.out.getCurrentMillis();
...
System.out.println("Elapsed time: " +
System.out.getCurrentMillis() - time);
The application will run more slowly because
LineNumberReader
extends BufferedReader
. Since line
number readers are buffered to begin with, mixing in a buffered
decorator will not improve performance; in fact, it will slow things down
because the buffered decorator must forward calls, which slows things
down.
Email
Thanks to everyone who sent
me email after the first installment of this column. I received voluminous
feedback with many interesting suggestions. As a notable example, Mark Riggle
wrote:
I have an idea that you may already plan to incorporate. As you say in the article, object composition is preferable to inheritance. Many of the Gang of Four patterns reflect this concept by using delegation to pass a method to an object that will do the work. Strategy and Decorator are certainly two such patterns. The problem for languages like Java is the required dispatching methods. The decorator object must implement the methods of the object it is decorating, and most will likely just directly pass on to the decorated object. This is needed so that the client code is left unchanged. So whenever the decorated class changes its signature, the decorators must also be updated. This is a coupling problem solvable using some of the dynamic features added to Java. The most important one now is the dynamic proxy. Because many of the methods added to classes are merely forwarding calls to a delegated object, the dynamic proxy lets you do exactly that without actually adding the method definitions...Thus, I think that the dynamic proxy and pattern use in Java go hand in hand, and a discussion on patterns needs to include a discussion on the dynamic proxy.
Mark is absolutely correct. Since JDK 1.3, Java directly supports the Proxy
pattern. The Proxy pattern is closely related to the Decorator pattern, and is
the topic for my next Java Design Patterns installment. In that
article, I will show you how to use the built-in JDK support for the Proxy (and
Decorator) patterns to implement the sort decorator example from this article.
About the author
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 in 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.
![]() |
![]() |
|
![]() |
Copyright © 2002 JavaWorld.com, an IDG Communications company |
![]() |
![]() |
![]() |