Decorator Design Pattern Using Java Decorator Design Pattern Using Java

Decorator design pattern is used to add a new feature on the existing object by wrapping it with a decorator class.

In this example, we will first create an interface Window interface and its implementation BasicWindow

interface Window {
    public String draw();
}

class BasicWindow implements Window {

    @Override
    public String draw() {
        return "Basic Window";
    }
}

Next, we want to decorate this Window with some border and scroll bar. We need a wrapper class for this which can wrap Window to add new features. For this, we create a class WindowDecorator which takes Window as constructor argument and also override draw() method to add new feature.

class WindowDecorator implements Window {

    protected Window window;

    public WindowDecorator(Window window) {
        this.window = window;
    }

    @Override
    public String draw() {
        return window.draw();
    }
}

We’ll now implement our wrapper class WindowDecorator and create two decorator classes BorderDecorator and ScrollDecorator. These decorators override draw() method to add new features like border and scroll bar.

class BorderDecorator extends WindowDecorator {

	public BorderDecorator(Window window) {
		super(window);
	}

	@Override
	public String draw() {
		return window.draw() + addBorder();
	}

	public String addBorder() {
		return " with Border";
	}
}

class ScrollDecorator extends WindowDecorator {

	public ScrollDecorator(Window window) {
		super(window);
	}

	@Override
	public String draw() {
		return window.draw() + addScroll();
	}

	public String addScroll() {
		return " and Scroll Bar";
	}
}

Let’s test our decorator classes

@Test
public void testDecorators() {
  
  Window basicWindow = new BasicWindow();
  assertEquals(basicWindow.draw(), "Basic Window");
  
  Window borderWindow = new BorderDecorator(basicWindow);
  assertEquals(borderWindow.draw(), "Basic Window with Border");

  Window borderWindowScrollable = new ScrollDecorator(borderWindow);
  assertEquals(borderWindowScrollable.draw(), "Basic Window with Border and Scroll Bar");
}

Few points from test:

  1. We have created a basicWindow object and decorated it using BorderDecorator
  2. Next we further decorated it using ScrollDecorator
  3. We can add as many as decorators at runtime to add new features to object.

Why Not Just Use Subclassing?

The obvious alternative is to create a BorderedWindow subclass, a ScrollableWindow subclass, and a BorderedScrollableWindow subclass for the combination. That works for two optional features but explodes combinatorially — N independent features need up to 2^N subclasses to cover every combination. Decorator avoids this by composing behavior at runtime instead of baking every combination into the type hierarchy at compile time: you build exactly the combination you need by wrapping objects, and a new feature only requires one new decorator class, not a new subclass for every existing combination it might be paired with.

Order Matters

Because each decorator wraps the previous result and appends its own text after delegating, the order you nest decorators in changes the output:

Window scrollFirst = new ScrollDecorator(basicWindow);
Window scrollThenBorder = new BorderDecorator(scrollFirst);

System.out.println(scrollThenBorder.draw());
// Basic Window and Scroll Bar with Border

Compare that to borderWindowScrollable from the test above, which produces "Basic Window with Border and Scroll Bar" — same two decorators, reversed nesting, different result. This is harmless for string concatenation, but in real decorators (compression, encryption, buffering, logging) the order can change behavior entirely — e.g. compress-then-encrypt produces a different byte stream than encrypt-then-compress, and only one of the two actually compresses well.

Real-World Example: java.io

This pattern isn’t just a textbook exercise — the JDK uses it heavily in java.io:

InputStream in = new BufferedInputStream(
                    new GZIPInputStream(
                        new FileInputStream("data.gz")));

FileInputStream is the concrete component, and GZIPInputStream / BufferedInputStream are decorators that each add one capability (decompression, buffering) without the base FileInputStream needing to know about either.

Common Pitfalls

  • Forgetting to override a method the decorator doesn’t care about. WindowDecorator.draw() delegates to window.draw() by default, so a decorator that overrides a different method but leaves draw() alone still behaves correctly. But if Window grows a second method and a decorator overrides draw() while forgetting to delegate the new method, calls to it silently bypass every decorator wrapped around the object — a bug that’s easy to miss because the code still compiles.
  • equals()/hashCode() on decorated objects. A BorderDecorator-wrapped window is not .equals() to the BasicWindow it wraps unless you explicitly implement equality to compare the underlying component — decorators change an object’s identity, not just its behavior.
  • Deep decorator chains are hard to debug. A stack trace through five nested decorators all calling draw() on each other can be harder to read than a single class with a boolean flag. Prefer Decorator when the number of feature combinations is genuinely open-ended; for a fixed, small set of combinations, a simpler builder or a couple of well-named subclasses may be easier to maintain.