Decorator Pattern: Adding Cool Toppings

Learn how to add features to objects dynamically with the Decorator pattern. Explore the ice cream analogy and see how to avoid 'Class Explosion' in your projects.

April 22, 20242 min read2 / 4

Welcome back! Today we explore a "Structural" pattern that helps us add cool features to our objects without making our code a mess: the Decorator Pattern.

The Problem: The "Class Explosion"

Imagine you are a software engineer at an ice cream shop. You start with a simple Cone. But then customers want:

  • A Chocolate scoop
  • A Strawberry scoop
  • Blueberry syrup
  • A Cherry on top

If you try to make a separate class for every single combination (like ChocolateStrawberryCone or BlueberryCherryCone), you will end up with thousands of classes!

The Solution: Toppings as Wrappers

The core idea of the Decorator pattern is that toppings are also ice creams. Every topping we add must still look and act like the original ice cream. This lets us keep "wrapping" things inside each other.

Step 1: The Base Interface

Every ice cream should be able to tell us its Price and its Description.

Java
public interface IceCream { int getPrice(); String getDescription(); }

Step 2: The Decorator (The Wrapper)

The ChocolateScoop is a decorator. It must take another ice cream in its constructor. It then adds its own price and description to whatever is already inside.

Java
public class ChocolateScoop implements IceCream { private IceCream iceCream; public ChocolateScoop(IceCream iceCream) { this.iceCream = iceCream; } @Override public int getPrice() { return iceCream.getPrice() + 40; } @Override public String getDescription() { return iceCream.getDescription() + " + Chocolate Scoop"; } }

Java vs. JavaScript: The Decorator Reality

1. In Java (Standard OOP)

In Java, we strictly use the interface-based wrapping approach. This is exactly how the Java I/O library works! When you write new BufferedReader(new FileReader("file.txt")), you are using the Decorator pattern to add "Buffering" stickers to a "File Reader."

2. In JavaScript / TypeScript

In the JS world, "Decorators" often refer to the @Decorator syntax (like in Angular or NestJS). These are special functions that modify classes or methods at runtime. However, for structural logic, the wrapping pattern is still common in UI frameworks like Flutter (wrapping widgets in Padding) or React (Higher-Order Components).


Real-World Examples

  • Java I/O: Wrapping streams to add functionality (Buffering, Compression).
  • Flutter: Wrapping widgets to add styling (Padding, Center, Theme).
  • Spring Boot: Adding security stickers to methods using annotations.

In our next part, we will look at how to save massive amounts of memory when dealing with thousands of objects: the Flyweight Pattern.