ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Using the Decorator Pattern

by Budi Kurniawan
02/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
By Marc Loy, Robert Eckstein, Dave Wood, James Elliott, Brian Cole

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

Next Pagearrow