0/0
Design Patterns

Decorator Pattern

A structural pattern that attaches new responsibilities to an object at runtime by wrapping it in another object β€” the wrapped onion grows one layer at a time, never one subclass at a time.

Explore Topics

🎁
Intent
Wrap, don't subclass
πŸ§ͺ Decorator Stack Lab β€” wrap the base text in any combination, any order
Base text:
Presets:
Available decorators Β· click to add a layer on TOP of the stack
πŸ§… Onion Β· outermost layer on top
#1BorderDecorator
#2UpperCaseDecorator
β˜…BasicText (ConcreteComponent)"Hello World"
πŸ“œ Construction expression
new BorderDecorator(
  new UpperCaseDecorator(
    new BasicText("Hello World")
  )
)
Walk the parens inside-out: each `new` adds a layer to the onion.
πŸ“€ outermost.render() output2 layers
═══════════
HELLO WORLD
═══════════
Try the same decorators in two different orders (β–² β–Ό). The output changes β€” that's the order-sensitivity of composition. Each layer applies its transformation to whatever the wrapped component produced.
🎁
Intent
Wrap, don't subclass

Decorator is a **structural** GoF pattern that lets you attach additional responsibilities to a single object **dynamically**. A decorator IS-A Component (so callers cannot tell the difference) AND HAS-A Component (so it can forward the work and add to it). The result is an onion: peel any layer and the inner object is still the same shape β€” but each layer added a fresh responsibility on the way in and on the way out.

Key Ideas
  • Add behavior to a **single instance**, not to a class β€” other instances of the same class are unaffected.
  • A decorator **IS-A** Component (same interface) AND **HAS-A** Component (the wrapped object) β€” that dual relationship is the whole pattern.
  • Composition happens at **runtime**: `new B(new A(plain))` is a totally different beast from `new A(new B(plain))`.
  • Often called the **Wrapper** pattern β€” every Decorator wraps another Component, and that Component may itself be a wrapper.
Strengths
  • ✦Extend a single object without touching the Component class or any of its siblings (Open/Closed Principle).
  • ✦Combine decorators in any order, any depth β€” choose the recipe at runtime, not compile time.
  • ✦Each decorator stays focused on one responsibility (Single Responsibility Principle).
Weaknesses
  • β–³Many small decorator classes β€” sometimes one per feature flag.
  • β–³Order is significant but invisible at the call site β€” `Hex(AddedText(p))` β‰  `AddedText(Hex(p))`.
  • β–³Equality and identity are tricky: `d.equals(d.inner)` is false even though both expose the same interface.
Used in:A **BasicCar** rolls off the line. The buyer chooses to upgrade it to **SportsCar** trim (spoiler, racing seats), then chooses to add the **LuxuryCar** package (leather, audio, sunroof) on top of that. Each upgrade wraps the existing car instance β€” no new subclass for "Sports+Luxury" needs to exist.
Code Snippets

Quiz

Intent Quiz
QUIZ 1/6
Which statement best captures the **intent** of the Decorator pattern?

Diagrams

Decorator Pattern β€” GoF structure (4 roles)
Loading diagram...
Runtime call flow β€” cd2.flushBuffer() walks inward, returns outward
Loading diagram...

Practice

Drag each class, method, or expression into the Decorator role it fills. Two items are traps β€” they belong to a DIFFERENT GoF pattern and should be sorted into the trap bucket.
EXERCISE 1/4
interface PrintText { void print(String s); }
abstract class JComponent (the contract every Swing widget speaks)
class FileInputStream extends InputStream (raw bytes from disk)
class PlainPrintText implements PrintText (no-frills core)
abstract class PrintTextDecorator implements PrintText { protected final PrintText inner; }
class FilterInputStream extends InputStream { protected InputStream in; }
class BufferedReader extends Reader (adds line-by-line buffering)
class HexDecorator extends PrintTextDecorator { ... appends hex view }
class JScrollPane (adds scrolling to any JComponent it wraps)
class LoggerSingleton { private static LoggerSingleton INSTANCE; }
static Logger getLogger() { return LoggerSingleton.INSTANCE; }
Component (the shared contract)
ConcreteComponent (the no-frills core)
Decorator (abstract base β€” IS-A and HAS-A)
ConcreteDecorator (adds responsibility)
NOT part of Decorator (belongs to a different pattern)
Order the construction lines to build the onion from inside-out: HEX wraps AddedText wraps Plain.
EXERCISE 2/4
Drag the lines into the correct order.
// β†’ produces: [TAG] X -> HEX: 0x5b 0x54 0x41 ... (TAG prepended, then HEX appended)
PrintText core = new PlainPrintText();
PrintText outer = new HexDecorator(middle);
PrintText middle = new AddedTextDecorator(core);
outer.print("X");
8 common myths about the Decorator pattern β€” true or false?
EXERCISE 3/4
A Decorator can only ADD behavior β€” it can never remove or subtract from the wrapped component's behavior.
The order in which decorators are stacked does not change the final result.
The Decorator pattern uses inheritance somewhere in its structure.
Decorator and Proxy have the same skeleton β€” they differ only in INTENT.
Forgetting to call `super.method()` in a ConcreteDecorator triggers a compile error.
`BufferedReader bw = new BufferedReader(new FileReader("x.txt"))` is an example of the Decorator pattern in the JDK.
A Decorator must add behavior on BOTH sides of the `super.method()` call.
Modeling N independent feature flags via inheritance requires N subclasses.
The decorator chain is silently swallowing the inner work β€” find the bug
EXERCISE 4/4
Click on the line you think contains the bug, then press Confirm.
1abstract class AbstractDecorator extends Printer {
2 protected Printer printer;
3 public void selectPrinterToFlush(Printer ptr) { this.printer = ptr; }
4 public void flushBuffer() {
5 if (printer != null) printer.flushBuffer();
6 }
7}
8
9class AuditDecorator extends AbstractDecorator {
10 @Override
11 public void flushBuffer() {
12 System.out.println("[AUDIT] flush requested");
13 }
14}
15
16// Caller:
17ConcretePrinter base = new ConcretePrinter();
18AuditDecorator audit = new AuditDecorator();
19audit.selectPrinterToFlush(base);
20audit.flushBuffer();
21// Expected: "CORE: actual flush" then "[AUDIT] flush requested"
22// Actual: "[AUDIT] flush requested" only β€” where did the core flush go?

References

πŸ“š

Gamma, Helm, Johnson, Vlissides β€” Design Patterns: Elements of Reusable Object-Oriented Software (GoF, 1994)

en.wikipedia.org

book
πŸ“„

Refactoring.guru β€” Decorator Pattern

refactoring.guru

article
πŸ“š

Freeman, Robson, Bates, Sierra β€” Head First Design Patterns, Chapter 3 (Decorator)

oreilly.com

book
πŸ“‹

Oracle Java I/O Tutorial β€” Buffered Streams and the Filter Stream Decorator

docs.oracle.com

docs
πŸ“„

SourceMaking β€” Decorator Design Pattern

sourcemaking.com

article