ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


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.

The constructor assigns the decorated component to child and adds it as a child component of the decorator. Note that here we use a BorderLayout as the layout for the decorator. This means the added JComponent will occupy the whole area of the decorator.

Now, take a look at the paint method. It first calls the parent class' paint method. This will paint the decorator as well as all of its children. We then draw a rectangle around the decorator area, after first getting the height and the width of the decorator.

Figure 1 shows a JFrame with three components:


Figure 1 -- comparing subclassing and the Decorator pattern

As Figure 1 shows, there is no difference in appearance between an instance of JBorderLabel and a decorated JLabel. This demonstrates that the Decorator pattern can be used as an alternative to subclassing. The third component proves that you can use the same Decorator to extend the behavior of an instance of a different class. In this sense, the decorator is superior, because you only need to create one class (BorderDecorator) to extend the functionality of objects of many different types.

Listing 3 gives the the code of the JFrame in Figure 1.

Listing 3 -- using the BorderDecorator class

package decorator;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Frame1 extends JFrame {

   JBorderLabel label1 =
      new JBorderLabel("JLabel Subclass");

   BorderDecorator label2 =
      new BorderDecorator(new JLabel("Decorated JLabel"));

   BorderDecorator checkBox1 =
      new BorderDecorator(new JCheckBox("Decorated JCheckBox"));

   public Frame1() {
      try {
         this.setDefaultCloseOperation(EXIT_ON_CLOSE);
         getContentPane().setLayout(null);
         label1.setBounds(new Rectangle(10, 10, 120, 25));
         this.getContentPane().add(label1, null);
         label2.setBounds(new Rectangle(10, 60, 120, 25));
	     this.getContentPane().add(label2, null);
         checkBox1.setBounds(new Rectangle(10, 110, 160, 25));
         this.getContentPane().add(checkBox1, null);
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      Frame1 frame1 = new Frame1();
      frame1.setBounds(0, 0, 200, 200);
      frame1.setVisible(true);
   }
}

The ResizableDecorator Class

The ResizableDecorator is a different type of decorator. It does not override the paint method of its parent class; instead, it adds a small button that, when clicked, resizes and restores itself. Listing 4 presents the code of the ResizableDecorator class.

Listing 4 -- the ResizableDecorator class

package decorator;

import javax.swing.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.Rectangle;

public class ResizableDecorator extends JComponent {

   // decorated component
   protected JComponent child;
   private JButton button = new JButton();
   boolean minimum = false;
   private Rectangle r;

   public ResizableDecorator(JComponent component) {
      child = component;
      this.setLayout(new BorderLayout());
      this.add(child);
      child.setLayout(null);
      button.setBounds(0, 0, 8, 8);
      child.add(button);
      button.addActionListener(new java.awt.event.ActionListener() {
         public void actionPerformed(ActionEvent e) {
            button_actionPerformed(e);
         }
      });
   }

   void button_actionPerformed(ActionEvent e) {
      if (minimum) {
         this.setBounds(r);
      }
      else {
         r = this.getBounds();
         this.setBounds(r.x, r.y, 8, 8);
      }
      minimum = !minimum;
   }
}

First note that this decorator also extends JComponent, and its constructor accepts a JComponent. In addition to a variable called child to reference the decorated component, it also declares a JButton.

The first three lines of its constructor add the decorated component as the child component of the decorator.

child = component;
this.setLayout(new BorderLayout());
this.add(child);

It then adds the JButton as a child component of the decorated component. The child's layout is null. The JButton will have a dimension of 8 x 8 pixels and is added to the left-hand corner of the decorated component.

child.setLayout(null);
button.setBounds(0, 0, 8, 8);
child.add(button);

Finally, it adds a listener to the JButton.

button.addActionListener(new java.awt.event.ActionListener() {
   public void actionPerformed(ActionEvent e) {
      button_actionPerformed(e);
   }
});

When added to a container, the component decorated using ResizableDecorator will have a small button on its left-hand corner. When clicked, this button will execute the code inside of the button_actionPerfomed method. First, the method checks the value of minimum. This boolean indicates whether or not the decorator size is at its minimum. Initially, the value is false, so the else block is executed.

else {
   r = this.getBounds();
   this.setBounds(r.x, r.y, 8, 8);
}

It assigns the current dimension to the Rectangle r and sets the bounds to 8 x 8, with the left-hand corner remaining at the current position. Then, it toggles the value of minimum.

minimum = !minimum;

When the JButton is clicked for the second time, the value of minimum is true. This time, the if block is executed:

if (minimum) {
   this.setBounds(r);
}

The code will restore the decorator to its previous size.

Figure 2 shows a JFrame with three decorated components, and its code is given in Listing 5.


Figure 2 -- three components decorated using ResizableDecorator

Listing 5 -- using the ResizableDecorator class

package decorator;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Frame2 extends JFrame {
   ResizableDecorator label1 =
      new ResizableDecorator(new JLabel(" Label1"));
   ResizableDecorator button1 =
      new ResizableDecorator(new JButton("Button"));
   BorderDecorator label2 =
      new BorderDecorator(new ResizableDecorator(
         new JLabel(" doubly decorated")
   ));

   public Frame2() {
      try {
         jbInit();
      }
      catch(Exception e) {
         e.printStackTrace();
      }
   }

   public static void main(String[] args) {
      Frame2 frame = new Frame2();
      frame.setBounds(0, 0, 200, 200);
      frame.setVisible(true);
   }

   private void jbInit() throws Exception {
      this.setDefaultCloseOperation(EXIT_ON_CLOSE);
      this.getContentPane().setLayout(null);
      label1.setBounds(new Rectangle(10, 10, 120, 25));
      label2.setBounds(new Rectangle(10, 60, 120, 25));
      button1.setBounds(new Rectangle(10, 110, 120, 25));
      this.getContentPane().add(label1, null);
      this.getContentPane().add(label2, null);
      this.getContentPane().add(button1, null);
   }
}

Note that you can decorate a JComponent using one or more decorators, just like the JLabel label2 in Listing 5. Also, if you need to work with many decorators, you may want to consider having an abstract Decorator class from which all decorator classes derive. This way, you can put the common methods in the abstract class.

Summary

This article has compared subclassing with the Decorator pattern, giving two examples of decorators in Swing. While the use of the Decorator pattern is very common to change the visual look of a Swing component, its use is not limited to changing the user interface.

Budi Kurniawan is a senior J2EE architect and author.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.