Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Strategy for success

The powerful Strategy design pattern aids object-oriented design

  • Print
  • Feedback

Page 2 of 3

Example 1. The wrong way to paint Swing borders

// A hypothetical JComponent.paintBorder method
protected void paintBorder(Graphics g) {
   switch(getBorderType()) {
      case LINE_BORDER:   paintLineBorder(g);
                          break;
      case ETCHED_BORDER: paintEtchedBorder(g);
                          break;
      case TITLED_BORDER: paintTitledBorder(g);
                          break;
      ...
   }
}


Example 1's hypothetical JComponent.paintBorder() method hard codes border painting in JComponent, which tightly couples that functionality and the JComponent class.

You can see the consequences if you tried to implement a new border type—you'd have to modify JComponent in at least three places: First, you would have to add a new integer value corresponding to your new border type. Second, you would have to add a case to the switch statement. And third, you would have to implement a paintXXXBorder() method, where XXX designates the border type.

Obviously, you won't have much success extending the preceding paintBorder() implementation. Not only would you find it difficult to extend paintBorder() with a new border type, but the JComponent class is not yours to modify in the first place—it's part of the Swing toolkit, meaning you would have to recompile the class and rebuild the entire toolkit. You would also have to require your users to use your renegade Swing version instead of the standard version, and you'd have work to do with the next Swing release. Also, because you've added new border-painting functionality to the JComponent class, every Swing component can now access that functionality whether you like it or not—you cannot restrict your new border to a particular component type.

Finally, Swing components would not be extensible if the JComponent class implemented its functionality with switch statements, as in Example 1.

What is the OO alternative? Decouple, using the Strategy pattern, JComponent from the code that paints borders, so you can vary the border painting algorithm without modifying the JComponent class. By employing the Strategy pattern, you encapsulate the concept that varies, in this case painting a border, and program to an interface, not an implementation by providing a Border interface. Let's see how the JComponent uses the Strategy pattern to paint borders. Example 2 lists the JComponent.paintBorder() method:

Example 2. The right way to paint Swing borders

// The actual implementation of the JComponent.paintBorder() method
protected void paintBorder(Graphics g) {
   Border border = getBorder();
   if (border != null) {
      border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
   }
}


The preceding paintBorder() method paints a component's border with a border object. Under this scheme, the border object, not the JComponent class, encapsulates the border painting algorithm.

Notice that JComponent passes a reference to itself (the this argument) to Border.paintBorder() in case a border must retrieve information about its component, a technique known as delegation. With delegation, one object delegates functionality to another object, passing a reference to itself (see "Take Control with the Proxy Design Pattern" (JavaWorld, February 2002)).

The JComponent class references a border, which is returned from the JComponent.getBorder() method, shown in Example 3 with its corresponding setter method:

Example 3. Setter and getter methods for a Swing component's border

...
private Border border;
...
public void setBorder(Border border) {
   Border oldBorder = this.border;
   this.border = border;
   firePropertyChange("border", oldBorder, border);
   if (border != oldBorder) {
      if (border == null || oldBorder == null || !(border.getBorderInsets(this).
                                    equals(oldBorder.getBorderInsets(this)))) {
         revalidate();
      }       
      repaint();
   }
}
...
public Border getBorder() {
   return border;
}


When you set a component's border with JComponent.setBorder(), the JComponent class fires a property change event, and if the new border differs sufficiently from the old border, the component repaints. The getBorder() method simply returns the Border reference.

Figure 1's class diagram illustrates the relationship between borders and the JComponent class.

Figure 1. Swing borders. Click on thumbnail to view full-size image.

The JComponent class maintains a private reference to a Border object. Notice that because Border is an interface, not a class, the Swing components can have any border that implements the Border interface. (That's what it means to program to an interface, not an implementation.)

Now that you've seen how JComponent implements the Strategy pattern to paint component borders, let's test the implementation's extensibility by creating a new border type.

Create a new border type

Figure 2 shows a Swing application with three panels. I fitted each panel with a custom border, each a HandleBorder instance. Drawing programs frequently use handle borders to move and resize objects.

Figure 2. A handle border



Example 4 lists the HandleBorder class:

Example 4. The HandleBorder class

import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class HandleBorder extends AbstractBorder {
   protected Color lineColor;
   protected int thick;
   public HandleBorder() {
      this(Color.black, 6);
   }
   public HandleBorder(Color lineColor, int thick) {
      this.lineColor = lineColor;
      this.thick = thick;
   }
   public void paintBorder(Component component, 
                                  Graphics g, int x, int y, int w, int h) {
      Graphics copy = g.create();
      if(copy != null) {
         try {
            copy.translate(x,y);
            paintRectangle(component,copy,w,h);
            paintHandles(component,copy,w,h);
         }
         finally {
            copy.dispose();
         }
      }
   }
   public Insets getBorderInsets() {
      return new Insets(thick,thick,thick,thick);
   }
   protected void paintRectangle(Component c, Graphics g,
                           int w, int h) {
      g.setColor(lineColor);
      g.drawRect(thick/2,thick/2,w-thick-1,h-thick-1);
   }
   protected void paintHandles(Component c, Graphics g,
                           int w, int h) {
      g.setColor(lineColor);
      g.fillRect(0,0,thick,thick); // upper left
      g.fillRect(w-thick,0,thick,thick); // upper right
      g.fillRect(0,h-thick,thick,thick); // lower left
      g.fillRect(w-thick,h-thick,thick,thick); // lower right
      g.fillRect(w/2-thick/2,0,thick,thick); // mid top
      g.fillRect(0,h/2-thick/2,thick,thick); // mid left
      g.fillRect(w/2-thick/2,h-thick,thick,thick); // mid bottom
      g.fillRect(w-thick,h/2-thick/2,thick,thick); // mid right
   }   
}


The HandleBorder class extends javax.swing.border.AbstractBorder and overrides paintBorder() and getBorderInsets(). While HandleBorder's implementation isn't important, it is important that we can easily create new borders types because Swing uses the Strategy pattern to draw component borders.

  • Print
  • Feedback

Resources