Chapter 12. The Decorator Pattern

The Decorator pattern provides us with a way to modify the behavior of individual objects without having to create a new derived class. Suppose we have a program that uses eight objects, but three of them need an additional feature. You could create a derived class for each of these objects, and in many cases this would be a perfectly acceptable solution. However, if each of these three objects requires different features, you would have to create three derived classes. Further, if one of the classes has features of both of the other classes, you begin to create complexity that is both confusing and unnecessary.

For example, suppose we wanted to draw a special border around some of the buttons in a toolbar. If we created a new derived button class, this means that all of the buttons in this new class would always have this same new border, when this might not be our intent.

Instead, we create a Decorator class that decorates the buttons. Then we derive any number of specific Decorators from the main Decorator class, each of which performs a specific kind of decoration. In order to decorate a button, the Decorator would have to be an object derived from the visual environment so that it can receive paint method calls and forward calls to other useful graphics methods to the object that it is decorating. This is another case in which object containment is favored over object inheritance. The Decorator is a graphical object, but it contains the object that it is decorating. It might intercept some graphical method calls and perform some additional computation, and then it might pass them on to the underlying object that it is decorating.

Decorating a CoolButton

Recent Windows applications such as Internet Explorer and Netscape Navigator have a row of flat, unbordered buttons that highlight themselves with outline borders when you move your mouse over them. Some Windows programmers call this toolbar a CoolBar and the buttons CoolButtons. There is no analogous button behavior in the JFC, but we can obtain that behavior by decorating a JButton. In this case, we decorate it by drawing plain gray lines over the button borders, erasing them.

Let's consider how to create this Decorator. Design Patterns suggests that Decorators should be derived from some general Visual Component class and then every message for the actual button should be forwarded from the Decorator. In Java, this is completely impractical because there are literally hundreds of method calls in this base JComponent class that we would have to reimplement. Instead, we derive our Decorator from the JComponent class. Then, since JComponent itself behaves as a Container, it will forward all method calls to the components that it contains. In this case, we simply add the button to the JComponent, and that button will receive all of the GUI method calls that the JComponent receives.

Design Patterns suggests that classes such as Decorator should be abstract and that you should derive all of your actual working (or concrete) Decorators from the abstract class. In this Java implementation, this is not necessary because the base Decorator class has no public methods other than the constructor, since all methods are of JComponent itself.

public class Decorator extends JComponent {
    public Decorator (JComponent c) {
        setLayout(new BorderLayout());
        add("Center", c);
    }
}

Now, let's look at how we could implement a CoolButton. All we really need to do is to draw the button as usual from the base class and then draw gray lines around the border to remove the button highlighting.

/** this class decorates a CoolButton so that
the borders are invisible when the mouse
is not over the button
*/
public class CoolDecorator extends Decorator {
    boolean    mouse_over:    //true when mouse over button
    JComponent thisComp;

    public CoolDecorator(JComponent c) {
    super(c);
    mouse_over = false;
    thisComp   = this;        //save this component
    //catch mouse movements in an inner class
    c.addMouseListener(new MouseAdapter() {
        //set flag when mouse over
        public void mouseEntered(MouseEvent e) {
            mouse_over = true;
            thisComp.repaint();
        }
        //clear flag when mouse is not over
        public void mouseExited(MouseEvent e) {
            mouse_over = false;
            thisComp.repaint();
        }
    });
}
//--------------
//paint the button
public void paint(Graphics g) {
        super.paint(g);    //first draw the parent button
        //if the mouse is not over the button
        //erase the borders
        if (! mouse_over) {
            Dimension size = super.getSize();
            size.setColor(Color.lightGray);
            g.drawRect(0, 0, size.width-1, size.height-1);
            g.drawLine(size.width-2, 0, size.width-2,
                       size.height-1-);
            g.drawLine(0, size.height-2, size.width-2,
                       size.height-2);
        }
   }
}

Using a Decorator

Now that we've written a CoolDecorator class, how do we use it? We simply create an instance of the CoolDecorator and pass it the button that it is to decorate. We can do all of this right in the constructor. Let's consider a simple program that has two CoolButtons and one ordinary JButton. We create the layout as follows:

super ("Deco Button");
JPanel jp = new JPanel();
getContentPane().add(jp);

jp.add( new CoolDecorator(new JButton("Cbutton")));
jp.add( new CoolDecorator(new JButton("Dbutton")));
jp.add(Quit = new JButton("Quit"));
Quit.addActionListener(this);

This program is shown in Figure 12.1, with the mouse hovering over one of the buttons.

Cbutton and Dbutton are CoolButtons, which are outlined when a mouse hovers over them. Here, Dbutton is outlined.

Figure 12.1. Cbutton and Dbutton are CoolButtons, which are outlined when a mouse hovers over them. Here, Dbutton is outlined.

Now that we see how a single decorator works, what about multiple decorators? Suppose that we want to decorate our CoolButtons with another decoration, say, a red diagonal line. Since the argument to any Decorator is just a JComponent, we could create a new Decorator with a Decorator as its argument. Let's consider the SlashDecorator, which draws that red diagonal line.

public class SlashDecorator extends Decorator {
    int x1, y1, w1, h1;

    public SlashDecorator(JComponent c) {
        super(c);
    }
    public void setBounds(int x, int y, int w, int h) {
        x1 = x; y1 = y;
        w1 = w; h1 = h;
        super.setBounds(x, y, w, h);
    }
    public void paint(Graphics g) {
        super.paint(g);
        g.setColor(Color.red);
        g.drawLine(0, 0, w1, h1);
    }
}

Here, we save the size and position of the button when it is created and then use those saved values to draw the diagonal line.

You can create the JButton with these two Decorators by just calling one and then the other.

jp.add(new SlashDecorator(
new CoolDecorator(new JButton("Dbutton"))));

This gives us a final program that displays the two buttons, as shown in Figure 12.2.

Dbutton is decorated with a SlashDecorator.

Figure 12.2. Dbutton is decorated with a SlashDecorator.

The Class Diagram

The class diagram in Figure 12.3 shows how we derived the Decorator base class from JComponent and how the concrete Decorators take another JComponent as an argument to determine the visual extent of the object that they decorate.

The UML class diagram for Decorators and two specific Decorator implementations.

Figure 12.3. The UML class diagram for Decorators and two specific Decorator implementations.

Decorating Borders in Java

One problem with this particular implementation of Decorators is that it is not easy to expand the size of the component being decorated because you add the component to a container and allow it to fill the container completely. If you attempt to draw lines outside the area of this component, they are clipped by the graphics procedure and not drawn at all.

The JFC provides its own series of Border objects that are a kind of decorator. As with a Decorator pattern, you can add a new Border object to any JComponent. There also is a way to add several borders. However, unlike the Decorator pattern, the Borders are not JComponents, and you do not have the flexibility to intercept and change specific events. In fact, it is more an example of the Strategy pattern than a Decorator.

The JFC defines several standard Border classes, given in the following table:

Class Description
BevelBorder(n)Simple two-line bevel; can be LOWERED or RAISED.
CompoundBorder(inner, outer)Allows you to add two borders.
EmptyBorder(top, left, bottom, right)Provides a blank border width specified on each side.
EtchedBorderCreates an etched border.
LineBorder(width, color)Creates a simple line border.
MatteBorderCreates a matte border of a solid color or a tiled icon.
SoftBeveledBorderCreates a beveled border with rounded corners.
TitledBorderCreates a border containing a title. Used to surround and label a JPanel.

These borders are simple to use in conjunction with the setBorder method of each JComponent. Figure 12.4 shows a normal JButton with a 2-pixel solid-line border, combined with a 4-pixel EmptyBorder and an EtchedBorder.

Dbutton has a 2-pixel solid line border, and Quit has a 4-pixel EmptyBorder and an EtchedBorder.

Figure 12.4. Dbutton has a 2-pixel solid line border, and Quit has a 4-pixel EmptyBorder and an EtchedBorder.

This was created with the following simple code:

JPanel jp = new JPanel();
getContentPane().add(jp);
jp.add( Cbutton = new JButton("Cbutton"));
jp.add( Dbutton = new JButton("Dbutton"));
EmptyBorder ep  = new EmptyBorder(4,4,4,4);
LineBorder lb   = new LineBorder(Color.black, 2);
Dbutton.setBorder(new CompoundBorder(lb, ep));
jp.add(Quit     = new JButton("Quit"));
EtchedBorder eb = new EtchedBorder();
Quit.addActionListener(this);
Quit.setBorder(eb);

One drawback of these Border objects is that they replace the default Insets values that determine the spacing around the component. Note that we had to add a 4-pixel Empty Border to the Dbutton to make it similar in size to the CButton. We did not do this for the Quit button, and it is therefore substantially smaller than the others.

You can also affect the border around a JButton, rather like a CoolButton, using the setBorderPainted(b) method. If b is false, the border is not drawn. You then can turn it back on and repaint when the button receives a mouseOver event.

Nonvisual Decorators

Decorators, of course, are not limited to objects that enhance visual classes. You can add or modify the methods of any object in a similar fashion. In fact, nonvisual objects can be easier to decorate because there may be fewer methods to intercept and forward.

While coming up with a single example is difficult, a series of Decorators do occur naturally in the java.io classes. Note the following in the Java documentation:

  • The class FilterInputStream itself simply overrides all methods of InputStream with versions that pass all requests to the underlying input stream. Subclasses of FilterInputStream may further override some of these methods as well as provide additional methods and fields.

The FilterInputStream class is thus a Decorator that can be wrapped around any input stream class. It is essentially an abstract class that doesn't do any processing but provides a layer on which the relevant methods have been duplicated. It normally forwards these method calls to the enclosed parent stream class.

The interesting classes derived from FilterInputStream include those in the following table:

Class Description
BufferedInputStreamAdds buffering to the stream so that every call does not cause I/O to occur.
CheckedInputStreamMaintains a checksum of bytes as they are read.
DataInputStreamReads primitive types (Long, Boolean, Float, and so on) from the input stream.
DigestInputStreamComputes a MessageDigest of any input stream.
InflateInputStreamImplements methods or uncompressing data.
PushbackInputStreamProvides a buffer from which data can be "unread," if during parsing you discover that you need to back up.

These Decorators can be nested, so a pushback, buffered input stream is quite possible.

As an example, consider a program to filter an e-mail stream to make it more readable. The user of an e-mail program types quickly and perhaps all in one case. The sentences can be made more readable if the first word of each sentence is capitalized. We accomplish this by interposing a FileFilter class, derived from FilterInputStream to read the data and transform it to mixed case. In Figure 12.5, we show a simple display, illustrating the original text on the left and the filtered text on the right.

An input stream and a filtered input stream

Figure 12.5. An input stream and a filtered input stream

The left-hand stream was read with the ordinary FileInputStream class as follows:

private String readNormal(File fl) {
        FileInputStream fread = null;
        String s = "";
        try {
            fread = new FileInputStream(fl);
            byte b[] = new byte[1000];
            int length = fread.read (b);
            fread.close ();
            s = new String(b, 0, length);
        }
        catch(FileNotFoundException e) {}
        catch(IOException ie) {}
    return s; 
    }

The right-hand pane was read by creating the same FileInputStream but passing it in a constructor to the FileFilter class. This class is derived from FileFilter class, where the readLine method simply calls the parent read method and then goes through the stream, inserting capitals after periods.

try {
        fread = new FileInputStream(f1);
        FileFilter ff = new FileFilter(fread);
        s = ff.readLine ();
        ff.close ();
    }

Note that we create a new readLine method that returns an already converted String rather than an array of bytes, as the parent classes do. We illustrate the class relationships in Figure 12.6.

The relationships between FileFilter, FilterInputStream, and the base classes.

Figure 12.6. The relationships between FileFilter, FilterInputStream, and the base classes.

Note that FilterInputStream contains an instance of InputStream.

Decorators, Adapters, and Composites

As noted in Design Patterns, an essential similarity exists among these classes that you might have recognized. Adapters also seem to "decorate" an existing class. However, their function is to change the interface of one or more classes to one that is more convenient for a particular program. Decorators add methods to particular instances of classes, rather than to all of them. Also possible is that a Composite consisting of a single item is essentially a Decorator. Once again, however, the intent is different.

Consequences of the Decorator Pattern

The Decorator pattern provides a more flexible way to add responsibilities to a class than by using inheritance, since it can add these responsibilities to selected instances of the class. It also allows customizing a class without creating subclasses high in the inheritance hierarchy. Design Patterns points out two disadvantages of the Decorator pattern. One is that a Decorator and its enclosed component are not identical; thus tests for object type will fail. The second is that Decorators can lead to a system with lots of little objects that look alike to the programmer trying to maintain the code. This can be a maintenance headache.

The Decorator and the Façade pattern, discussed next, evoke similar images in building architecture. However, in design pattern terminology, Façade is a way of hiding a complex system inside a simpler interface, while Decorator adds function by wrapping a class.

Thought Questions

  1. When someone enters an incorrect value in a cell of a JTable, you might want to change the color of the row to indicate the problem. Suggest how you could use a Decorator for this purpose.

  2. A mutual fund is a collection of stocks. Each one consists of an array or Vector of prices over time. Suggest how a Decorator can be used to produce a report of stock performance for each stock and for the whole fund.

Programs on the CD-ROM

Programs Description

DecoratordecoWindow.java

Shows the CoolButton Decorator.

DecoratorslashWindow.java

Shows the SlashButton decorator.

DecoratororderWindow.java

Shows how the JFC Border classes are used.

DecoratorFilterStreamDecoStream.java

Shows a use of the FilterInputStream class as a Decorator.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset