Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
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
Everybody who reads the famous book Design Patterns knows the Decorator pattern. Let's, for a moment, forget what we know about this concept and try to understand Decorator from our everyday experience.
A decorator is something or somebody who decorates something or (maybe) somebody. A decorator slightly changes the decorated object, yet doesn't change its nature. Examples: A frame decorates the picture. Makeup decorates a woman's face. A husband decorates his wife and vise versa.
Let's discuss some examples from our specialty, computer science:
JScrollPane decorates portions of JComponent (a.k.a. View). In this case, the nature of JComponent doesn't change, but a new property (scrollable) is added.
BufferedInputStream is a decorator of InputStream. BufferedInputStream is an InputStream, but works more quickly, because it uses an inside buffer.
DebugButton, which is the same as a JButton, but writes a message to the log file when a button is pressed. DebugButton is a decorator of JButton, because it slightly changes JButton but doesn't change its nature.
ScrollOverButton, which adds the scroll-over behavior to JButton. Adding scroll-over makes the button look flat when the cursor is out of the button frame, otherwise the button is prominent.
Evidently, ScrollOverButton is a decorator of JButton.
Now, let's consider Decorator's implementation issues. There are three types of implementations:
In this article, I discuss these implementation types and their pros and cons.
A generic and simple idea first occurs to programmers when thinking about decorators: write the class, which extends the decorated class. The additional behavior can be implemented as the new methods or as the changing implementation of existing methods:
public class DebugButton extends JButton
{
public DebugButton()
{
addActionListener(new ActionListener()
{
System.out.println("debug message");
});
}
}
In the same way, we can implement ScrollOverButton: instead of ActionListener, we can add MouseListener. MouseListener callbacks (methods) will change the JButton border from EmptyBorder to RaisedBevelBorder, when mouseEntered() is called, and from RaisedBevelBorder to EmptyBorder, when mouseExited() is called.
For BufferedInputStream, implementation is also simple. Every read method can ask the inner buffer to get the next portion of bytes. If the buffer
is empty, it may be filled by an appropriate super.read() method. For JScrollPane, implementation is much more complicated. I'll explain why below.
Let's discuss the pros and cons of the inheritance implementation.
ScrollOverButton instead of JButton in every part of our application, but we cannot use JScrollPane instead of the internal object. Using the inheritance implementation, we can keep the type of the decorated class.
ScrollOverButton and DebugButton and we need a button that merges the ScrollOver and Debug behaviors together. In this case, the forced choice would be to write the new class ScrollOverDebugButton. If we have the ScrollOverDebugButton implementation, keeping both the ScrollOverButton and DebugButton implementations would be strange. We can add to ScrollOverDebugButton two pairs of methods to turn on/off debug and scroll-over behavior:public void setDebug(boolean b); public boolean isDebug(); public void setScrollOver(boolean b); public boolean isScrollOver();
Let's consider we have to write a decorator that has U1, U2, ... Un behaviors. We'll write a class named U1U2...UnButton, which has 2n methods:
public void setU(boolean b); public boolean getU;();
In this case, adding the new behavior (Un+1) to the decorator requires adding two methods to the class and changing the decorator implementation. That conflicts with
object-oriented design principles and has fatal consequences. Note: The javax.swing.JButton was implemented this way.
The inheritance type of Decorator implementation is not as simple as it first appears. In many cases, we don't know what kind of decorators we'll need for the future; as a result, using this type of implementation will prove difficult for extension and conflicts with object-oriented design principles.
With the wrapper implementation, the main idea is to wrap the decorated object into the Decorator pattern. The Decorator pattern forwards requests to the wrapped object and can perform the new functionality before or after forwarding, or as separate methods.
Now, let's return to our examples and consider them in terms of the wrapper implementation:
BufferedInputStream is a wrapper of InputStream (see java.io.BufferedInputStream in the Java SDK distribution). Regardless of the fact that it extends InputStream, it is a wrapper. In the constructor, BufferedInputStream gets another InputStream, keeps it in the instance variable, and forwards the requests to the underlining InputStream. Note: We can use BufferedInputStream every time we use InputStrem before decoration.
JScrollPane is also a wrapper. It forwards requests to a wrapped Component (called View). Note: We cannot use JScrollPane instead of its underlining component because it doesn't support all of View's functionality. For example, getFont() of JScrollPane returns JScrollPane's font, not View's font.
DebugButton in this way:public class DebugButton extends JButton implements ActionListener
{
private JButton butt = null;
public DebugButton(JButton butt)
{
this.butt=butt;
butt.addActionListener(this);
}
// ActionListener
public void actionPerformed(ActionEvent e)
{
System.out.println("debug message for button" + butt);
}
. . . . . . . .
/* About 180 methods look like this:
any JButton method M(params)
{
butt.M(params)
}
*/
This implementation keeps the type of decorated object (it extends JButton), but it doesn't look so straightforward.
Note: We cannot use java.lang.reflect.Proxy for redirection because JButton is a class, not an interface.
Another implementation may be:
public class DebugButton extends JComponent implements ActionListener
{
private JButton butt = null;
public DebugButton(JButton butt)
{
this.butt=butt;
butt.addActionListener(this);
}
public JButton getUnderlineButton()
{
return butt;
}
// ActionListener
public void actionPerformed(ActionEvent e)
{
System.out.println("debug message for button" + butt);
}
. . . . . . . .
/* There we can implement some (not many) selected methods like
get/setFont, get/setBackground , etc.
*/
}
This implementation is much more simple, but DebugButton doesn't extend JButton, and we cannot use DebugButton instead of JButton. The JScrollPane implementation is based on this idea.
Archived Discussions (Read only)