Strategy for success

The powerful Strategy design pattern aids object-oriented design

Back in 1984, I graduated from college with a mechanical engineering degree and went to work as a software engineer. After teaching myself C programming, by 1985 I was busily developing a 50,000-line graphical user interface (GUI) for Unix. That was fun.

By the end of 1985, my program complete, I was ready to move to other projects—or so I thought. I soon received a slew of bug reports and enhancement requests, so I started wading through my 50,000 lines to make fixes. That was hard.

I had created a veritable house of cards that came tumbling down almost daily. And that house of cards fell easily; even the most innocuous changes often left me debugging for hours just to restore the program's stability.

I thought I had stumbled upon an important software development tenet: have fun during development, then look for a new job after deployment. In fact, however, my difficulties stemmed from my ignorance of the most fundamental tenet of object-oriented (OO) software development: encapsulation. My program was a huge switch-statement collection that invoked different functions under different circumstances—resulting in tightly coupled and difficult-to-change software.

In this Java Design Patterns installment, I discuss perhaps the most fundamental design pattern: Strategy. If I had known about the Strategy pattern in 1984, I would have avoided a great deal of work.

The Strategy pattern

In Chapter 1 of the Gang of Four's (GOF) Design Patterns, the authors discuss several OO design principles comprising the core of many patterns. The Strategy pattern embodies two such principles—encapsulate the concept that varies and program to an interface, not an implementation. The Design Patterns authors define the Strategy pattern as:

Define a family of algorithms, encapsulate each one, and make them interchangeable. [The] Strategy [pattern] lets the algorithm vary independently from clients that use it.

The Strategy pattern lets you build software as a loosely coupled collection of interchangeable parts, in contrast to a monolithic, tightly coupled system. That loose coupling makes your software much more extensible, maintainable, and reusable.

To teach the Strategy pattern, I first illustrate how Swing uses the Strategy pattern to draw borders around its components. Then I discuss how Swing benefits by using the Strategy pattern, and finally I explain how you can implement the Strategy pattern in your own software.

Swing borders

You can draw borders around almost all Swing components, including panels, buttons, lists, and so on. Swing provides numerous border types for its components: bevel, etched, line, titled, and even compound. Borders for Swing components are drawn by the JComponent class, which acts as the base class for all Swing components by implementing functionality common to all Swing components.

JComponent implements paintBorder(), a method for painting borders around components. Swing's creators could have implemented paintBorder() like the method in Example 1:

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.

Example 5 lists the Swing application shown in Figure 2:

Example 5. Use handle borders

import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
public class Test extends JFrame {
   public static void main(String[] args) {
      JFrame frame = new Test();
      frame.setBounds(100, 100, 500, 200);
      frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
      frame.show();
   }
   public Test() {
      super("Creating a New Border Type");
      Container contentPane = getContentPane();
      JPanel[] panels = { new JPanel(), 
                     new JPanel(), new JPanel() };
      Border[] borders = { new HandleBorder(),
                     new HandleBorder(Color.red, 8),
                     new HandleBorder(Color.blue, 10) };
      contentPane.setLayout(
               new FlowLayout(FlowLayout.CENTER,20,20));
      for(int i=0; i < panels.length; ++i) {
         panels[i].setPreferredSize(new Dimension(100,100));
         panels[i].setBorder(borders[i]);
         contentPane.add(panels[i]);
      }
   }
}

The preceding application creates three panels (instances of javax.swing.JPanel) and three borders (instances of HandleBorder). Notice how you can easily specify borders for those panels by calling JComponent.setBorder().

Recall from Example 2 that a reference to a component passed to the component's border when JComponent calls Border.paintBorder()—a form of delegation. As I mentioned earlier, developers frequently use delegation with the Strategy pattern. The HandleBorder class does not use that component reference, but some borders use that reference to obtain information from their component. For example, Example 6 lists the paintBorder() method for such a border, the javax.swing.border.EtchedBorder:

Example 6. Some Swing borders retrieve information from their components

// The following listing is from
// javax.swing.border.EtchedBorder
public void paintBorder(Component component, Graphics g, int x, int y, 
                         int width, int height) {
   int w = width;
   int h = height;
   
   g.translate(x, y);
   
   g.setColor(etchType == LOWERED? getShadowColor(component) : getHighlightColor(component));
   g.drawRect(0, 0, w-2, h-2);
   
   g.setColor(etchType == LOWERED? getHighlightColor(component) : getShadowColor(component));
   g.drawLine(1, h-3, 1, 1);
   g.drawLine(1, 1, w-3, 1);
   
   g.drawLine(0, h-1, w-1, h-1);
   g.drawLine(w-1, h-1, w-1, 0);
   
   g.translate(-x, -y);
}

The javax.swing.border.EtchedBorder.paintBorder() method uses its component reference to find its component's shadow and highlight colors.

Implement the Strategy pattern

The Strategy pattern, as one of the simpler design patterns, proves easy to implement in your own software:

  1. Implement a Strategy interface for your strategy objects
  2. Implement ConcreteStrategy classes that implement the Strategy interface, as appropriate
  3. In your Context class, maintain a private reference to a Strategy object.
  4. In your Context class, implement public setter and getter methods for the Strategy object

The Strategy interface defines your Strategy objects' behavior; for example, the Strategy interface for Swing borders is the javax.swing.Border interface.

The concrete ConcreteStrategy classes implement the Strategy interface; for example, for Swing borders, the LineBorder and EtchedBorder classes are ConcreteStrategy classes.

The Context class uses Strategy objects; for example, for Swing borders, the JComponent class is a Context class.

You may also examine your existing classes to see if they have tightly coupled functionality that's a candidate for Strategy objects. Typically, such candidates include switch statements similar to the one I discussed at the beginning of this article.

Homework

Some Swing components have more sophisticated rendering and editing requirements than others. Discuss how Swing uses the Strategy pattern in its list class (javax.swing.JList) to render list cells.

Homework from last time

Your last assignment asked you to reimplement the TableBubbleSortDecorator I first discussed in "Decorate Your Java Code" (JavaWorld, December 2001) with the JDK's built-in Proxy pattern support.

As a convenience, I created an abstract Decorator class that implements the java.lang.reflect.InvocationHandler interface. The Decorator class also references the decorated object (or in Proxy pattern parlance, the real subject). Example 1H lists the Decorator class:

Example 1H. An abstract Decorator class

import java.lang.reflect.InvocationHandler;
public abstract class Decorator implements InvocationHandler {
   // The InvocationHandler interface defines one method:
   // invoke(Object proxy, Method method, Object[] args). That
   // method must be implemented by concrete (meaning not 
   // abstract) extensions of this class.
   private Object decorated;
   protected Decorator(Object decorated) {
      this.decorated = decorated;
   }
   protected synchronized Object getDecorated() {
      return decorated;
   }
   protected synchronized void setDecorated(Object decorated) {
      this.decorated = decorated;
   }
}

Although the Decorator class claims to implement the InvocationHandler interface, it does not implement the only method defined by that interface: invoke(Object proxy, Method method, Object[] methodArguments). Because of that omission, the Decorator class is abstract, and Decorator extensions must implement the invoke() method if they wish to be concrete.

The Decorator class acts as a base class for all decorators. Example 2H lists a Decorator extension specific to table sort decorators. Notice that TableSortDecorator does not implement the invoke() method, and therefore is also an abstract class:

Example 2H. TableSortDecorator, revised

import javax.swing.table.TableModel;
import javax.swing.event.TableModelListener;
public abstract class TableSortDecorator 
                                 extends Decorator 
                                 implements TableModelListener {
   // Concrete extensions of this class must implement 
   // tableChanged from TableModelListener
   abstract public void sort(int column);
   public TableSortDecorator(TableModel realModel) {
      super(realModel);
   }
}

Now you can write a TableBubbleSortDecorator that uses the JDK's built-in Proxy pattern support:

Example 3H. TableBubbleSortDecorator, revised

import java.lang.reflect.Method;
import javax.swing.table.TableModel;
import javax.swing.event.TableModelEvent;
public class TableBubbleSortDecorator extends TableSortDecorator {
   private int indexes[];
   private static String GET_VALUE_AT = "getValueAt";
   private static String SET_VALUE_AT = "setValueAt";
   public TableBubbleSortDecorator(TableModel model) {
      super(model);
      allocate();
   }
   // tableChanged is defined in TableModelListener, which
   // is implemented by TableSortDecorator.
   public void tableChanged(TableModelEvent e) {
      allocate();   
   }
   // invoke() is defined by the java.lang.reflect.InvocationHandler
   // interface; that interface is implemented by the 
   // (abstract) Decorator class. Decorator is the superclass
   // of TableSortDecorator.
   public Object invoke(Object proxy, Method method, 
                                         Object[] args) {
      Object result = null;
      TableModel model = (TableModel)getDecorated();
      if(GET_VALUE_AT.equals(method.getName())) {
         Integer row = (Integer)args[0], 
                 col = (Integer)args[1];
         result = model.getValueAt(indexes[row.intValue()], 
                                           col.intValue());
      }
      else if(SET_VALUE_AT.equals(method.getName())) {
         Integer row = (Integer)args[1],
                 col = (Integer)args[2];
         model.setValueAt(args[0], indexes[row.intValue()],
                                   col.intValue());
      }
      else {
         try {
            result = method.invoke(model, args);
         }
         catch(Exception ex) {
            ex.printStackTrace(System.err);
         }
      }
      return result;
   }
   // The following methods perform the bubble sort ...
   public void sort(int column) {
      TableModel model = (TableModel)getDecorated();
      int rowCount = model.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 = (TableModel)getDecorated();
      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[((TableModel)getDecorated()).
                          getRowCount()];
      for(int i=0; i < indexes.length; ++i) {
         indexes[i] = i;         
      }
   }
}

With the JDK's built-in Proxy pattern support and a well-designed base class, you can easily implement decorators by extending Decorator and implementing invoke().

Email

In an email to me, Ken Ballard wrote:

A toolbar shows certain buttons depending on what node I select in a tree. I created a toolbar decorator that takes a toolbar (JToolBar) in its constructor. The decorator has a showButtonForNode() method that changes the toolbar's buttons depending upon the node. I invoke the showButtonForNode() method in the valueChanged() method of a tree selection listener.

Is this a good use of the Decorator pattern?

Numerous design patterns let you extend functionality; for example, in Java Design Patterns you've seen how to extend functionality with the Proxy, Decorator, and Strategy patterns. You can have difficulty knowing which pattern to use in a specific situation because groups of patterns achieve the same goal (for example, Proxy, Decorator, and Strategy all extend functionality). Closely related patterns' subtle differences determine which represents the best choice for a specific situation. Sometimes, more than one design pattern can be appropriate.

The Decorator pattern's major selling point: it can combine multiple behaviors at runtime; for example, in the "Homework from Last Time" section from "Take Control with the Proxy Design Pattern," I showed how to combine sorting and filtering for a Swing table:

TableSortDecorator sortDecorator = new TableBubbleSortDecorator(table.getModel());
TableFilterDecorator filterDecorator = new TableHighPriceFilter(sortDecorator);
table.setModel(filterDecorator);

In the preceding code, a filter decorator decorates a sort decorator, which in turn decorates a table's model; as a result, the table model in question sorts and filters its data.

Ken would not likely combine switching toolbar buttons with other behavior, as is the case for sorting and filtering, so the Decorator pattern may prove overkill. In this case, the Proxy pattern seems more appropriate, as it also extends functionality by fixing the relationship between the proxy and the real subject at compile time, but does not compose behaviors at runtime.

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 with numerous object-oriented languages for 18 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.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies