Using the Decorator Pattern
by Budi Kurniawan02/05/2003
Java programmers know that they can change the behavior or extend
functionality of a class by extending the class. This is called inheritance, and is an
important feature of object-oriented programming. For example, if you want a
Swing label that draws a border, you can subclass the
javax.swing.JLabel class. However, subclassing is not always
appropriate. Sometimes inheritance is impractical and you have to resort to
some other way, such as using the Decorator pattern. This article explains the
Decorator pattern and when to subclass and when to decorate. The classes used
in this application reside inside the package called decorator and
can be downloaded here.
The Java language provides the keyword extends for
subclassing a class. Those with enough knowledge of object-oriented
programming know how powerful subclassing, or extending a class, is. By
extending a class, you can change its behavior. Take, for example, the
JBorderLabel class in Listing 1. The new class, called
JBorderLabel, extends the javax.swing.JLabel class. It
has same look and behavior as the JLabel class, with the addition
of a border.
Listing 1 -- the JBorderLabel class, an example of
subclassing
package decorator;
import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.Icon;
public class JBorderLabel extends JLabel {
public JBorderLabel() {
super();
}
public JBorderLabel(String text) {
super(text);
}
public JBorderLabel(Icon image) {
super(image);
}
public JBorderLabel(String text, Icon image, int horizontalAlignment) {
super(text, image, horizontalAlignment);
}
public JBorderLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
To understand how JBorderLabel works, you need to understand
how Swing draws its components.
The JLabel class, like all Swing components, derives from
javax.swing.JComponent. Swing calls the JComponent
class' paint method to draw its user interface. To modify the way
a component paints its user interface, override its public paint
method. Here is the signature of JComponent's paint
method.
public void paint(Graphics g)
The Graphics object that the paint method
receives represents its drawing surface. To optimize painting, the
paint method is divided into three protected methods:
paintComponent, paintBorder, and
paintChildren. paint calls these methods, passing
the Graphics instance it received. The signatures of these three
methods are as follows:
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
You can customize the painting of a Swing component by overriding any or
all of these methods -- or even the paint method.
The JBorderLabel class overrides the
paintComponent method of javax.swing.JComponent. The
paintComponent method in JBorderLabel first calls the
paintComponent method of the parent class, in effect drawing a
JLabel. It then obtains its own height and width, drawing
a rectangle with the drawRect method of the
java.awt.Graphics instance. Figure 1 shows an instance of the
JBorderLabel class as the first component of a frame. As you can
see, it looks like a JLabel, but with a border.
|
Related Reading
Java Swing |
While subclassing works fine here, there are cases in which it is not
appropriate or practical. If you want this behavior (i. e., drawing a border)
for other components, then you end up with many subclasses. In Listing 1,
subclassing looks simple because you only override one method. However, as
your code grows more complicated, having too many subclasses creates
maintenance problems and makes your code more error-prone. (You must also
reproduce all of the parent class constructors that your subclass needs to
support, just like the JBorderLabel class.) In cases like this,
it is better to use the Decorator pattern.
The Decorator Pattern
The book Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al, classifies the Decorator pattern as a structural pattern. This pattern offers a flexible alternative to subclassing. The main difference between subclassing and the Decorator pattern is this: with subclassing, you work with the class, whereas in the Decorator pattern, you modify objects dynamically. When you extend a class, the change you make to the child class will affect all instances of the child class. With the Decorator pattern, however, you apply changes to each individual object you want to change.
When writing a decorator class for modifying the user interface of Swing
components, it is very important to understand the JComponent
class. I have explained how JComponent paints its user interface
in the previous section, and you can look up the documentation for all of the
members of this class. Be aware that a JComponent can have child
components; these children will be painted when the JComponent is
painted.
Create a Swing decorator by extending JComponent with a
constructor that accepts a JComponent. Pass any Swing object whose
behavior needs to be changed to the decorator. Inside the decorator class, add
the component as a child component. Instead of adding a Swing component to a
JFrame or JPanel or other container, pass it to the
decorator then add the decorator to the JFrame or
JPanel. Because a decorator is also a JComponent,
the container will not know the difference. As a result, the decorator is now a
child component of the container. When the container gives the decorator a
chance to draw itself, the decorator's paint method will be
invoked.
For example, suppose you have a JLabel that you want to add to
a JFrame called frame1. Use code similar to the
following:
frame.getContentPane().add(new JLabel("a label"));
To decorate your JLabel with MyDecorator, the
code is similar. (Remember that the MyDecorator class' constructor
accepts a JComponent):
frame.getContentPane().add(new MyDecorator(new JLabel("a label")));
This article demonstrates two examples of the Decorator pattern. The first
one is called BorderDecorator. This class is used to decorate a
JComponent so that it has a border. When added to a
JFrame, a JLabel that has been decorated using
BorderDecorator looks the same as an instance of
JBorderLabel; however, no subclassing is necessary. Better
still, you can pass any Swing component to BorderDecorator and all
of them will be given a border. In this example, you create one class
(BorderDecorator) to change the behavior of instances of different
types.
The second example is ResizableDecorator. This decorator adds
a small button to the left-hand corner of any Swing component passed to it.
When the user clicks the button, the component will be minimized to the
button.
The BorderDecorator Class
Let us start with BorderDecorator. This class represents a
decorator that adds a border to a Swing component. See Listing 2 for its
code.
Listing 2 -- the BorderDecorator class
package decorator;
import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
public class BorderDecorator extends JComponent {
// decorated component
protected JComponent child;
public BorderDecorator(JComponent component) {
child = component;
this.setLayout(new BorderLayout());
this.add(child);
}
public void paint(Graphics g) {
super.paint(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
First note that BorderDecorator extends
JComponent and that its constructor accepts another
JComponent to be decorated. The BorderDecorator class
also defines a JComponent called child to reference
the decorated JComponent.
Pages: 1, 2 |