Skip to content

Decorator

Dynamically add extra responsibilities to an object. Compared to generating subclasses, this approach is more flexible for extending functionality.

The Decorator pattern is a method for dynamically adding functionality to an instance of an object at runtime.

We’ve actually discussed the Decorator pattern in the section on the IO Filter pattern. In the Java standard library, InputStream is an abstract class, and classes like FileInputStream, ServletInputStream, and Socket.getInputStream() are final data sources.

If we want to add functionalities like buffering, signature calculation, or encryption/decryption to these data sources, we would need a total of 9 subclasses (3 final data sources × 3 functionalities). This would lead to a combinatorial explosion of subclasses, which is clearly not a viable design.

The purpose of the Decorator pattern is to layer additional functionalities onto the original data source using decorators, allowing us to achieve the desired functionality through composition.

For example, to add buffering and decompression features to FileInputStream, we can implement it as follows:

java
// Create the original data source:
InputStream fis = new FileInputStream("test.gz");
// Add buffering functionality:
InputStream bis = new BufferedInputStream(fis);
// Add decompression functionality:
InputStream gis = new GZIPInputStream(bis);

Or, we can write it all at once:

java
InputStream input = new GZIPInputStream( // Second layer of decoration
                        new BufferedInputStream( // First layer of decoration
                            new FileInputStream("test.gz") // Core functionality
                        ));

Notice that BufferedInputStream and GZIPInputStream both inherit from FilterInputStream, which serves as an abstract decorator. Here’s a diagram representing the Decorator pattern:

             ┌───────────┐
             │ Component │
             └───────────┘

      ┌────────────┼─────────────────┐
      │            │                 │
┌───────────┐┌───────────┐     ┌───────────┐
│ComponentA ││ComponentB │...  │ Decorator │
└───────────┘└───────────┘     └───────────┘

                              ┌──────┴──────┐
                              │             │
                        ┌───────────┐ ┌───────────┐
                        │DecoratorA │ │DecoratorB │...
                        └───────────┘ └───────────┘

The top-level Component is the interface, corresponding to the abstract class InputStream in IO. ComponentA and ComponentB are actual subclasses, corresponding to data sources like FileInputStream and ServletInputStream. The Decorator serves as an abstract decorator that implements various additional functionalities, corresponding to FilterInputStream in IO. Each class derived from Decorator represents a specific decorator, such as BufferedInputStream or GZIPInputStream.

Benefits of the Decorator Pattern

The Decorator pattern effectively separates core functionality from additional functionality. The core functionality refers to data sources like FileInputStream that actually read data, while additional functionality includes buffering, compression, and decryption. If we want to add core functionality, we create a new subclass of Component, such as ByteInputStream. If we want to add additional functionality, we create a new subclass of Decorator, such as CipherInputStream. Both parts can be independently extended, allowing the caller to freely compose additional functionalities, greatly enhancing flexibility.

Designing a Complete Decorator Pattern

Let’s take an example where we need to render HTML text, with additional effects like bold, italic, and underline. To achieve dynamic effects, we can use the Decorator pattern.

First, we need to define the top-level interface TextNode:

java
public interface TextNode {
    // Set text:
    void setText(String text);
    // Get text:
    String getText();
}

For a core node, such as <span>, it should directly inherit from TextNode:

java
public class SpanNode implements TextNode {
    private String text;

    public void setText(String text) {
        this.text = text;
    }

    public String getText() {
        return "<span>" + text + "</span>";
    }
}

Next, to implement the Decorator pattern, we need an abstract Decorator class:

java
public abstract class NodeDecorator implements TextNode {
    protected final TextNode target;

    protected NodeDecorator(TextNode target) {
        this.target = target;
    }

    public void setText(String text) {
        this.target.setText(text);
    }
}

The NodeDecorator class holds a reference to a TextNode, which is the instance to which we will add functionality. We can now implement a bold functionality:

java
public class BoldDecorator extends NodeDecorator {
    public BoldDecorator(TextNode target) {
        super(target);
    }

    public String getText() {
        return "<b>" + target.getText() + "</b>";
    }
}

Similar classes can be created for ItalicDecorator, UnderlineDecorator, etc. The client can freely combine these decorators:

java
TextNode n1 = new SpanNode();
TextNode n2 = new BoldDecorator(new UnderlineDecorator(new SpanNode()));
TextNode n3 = new ItalicDecorator(new BoldDecorator(new SpanNode()));
n1.setText("Hello");
n2.setText("Decorated");
n3.setText("World");

System.out.println(n1.getText());
// Output: <span>Hello</span>

System.out.println(n2.getText());
// Output: <b><u><span>Decorated</span></u></b>

System.out.println(n3.getText());
// Output: <i><b><span>World</span></b></i>

Exercise

Use the Decorator pattern to add a <del> tag to represent deletion.

Summary

Using the Decorator pattern, you can independently increase core functionality and additional functionality, with neither affecting the other. You can dynamically add any number of additional functionalities to core functionality at runtime.

Decorator has loaded