Structural - Decorator Pattern

This Pattern helps to add more functionality over the base object.

Imagine you are in a pizza shop. You start with a plain pizza, like a Farmhouse or Margherita. The pizza is yummy on its own, but you can make it even tastier by adding things on top—like cheese or mushrooms! Every time you add something (like extra cheese or mushrooms), you pay a little more. The decorator pattern is like putting these toppings on your pizza one by one—each makes the pizza special, and adds to the total cost. You don’t change the pizza itself. You just wrap it with more yummy things, making it better every time.

It’s just like decorating your pizza with toppings! Each topping is a “decorator”—it makes the pizza more fun and delicious, and you can choose which ones you want, in any order.

Let's

  • Start with FarmhousePizza (base pizza).

  • Add ExtraCheeseDecorator (add cheese).

  • Add ExtraMushroomDecorator (add mushrooms).

  • The cost is the price of the pizza plus toppings!

Low Level Design

Requirement

Create a Pizza shop where pizza can be made with different toppings.

Implementation

The Abstract Classes

public abstract class BasePizza {
	abstract int cost();
}
public abstract class ToppingDecorator extends BasePizza {

}

The Concrete Classes

public class FarmhousePizza extends BasePizza {
	@Override
	int cost() {
		return 200;
	}
}
public class MargheritaPizza extends BasePizza {
	@Override
	int cost() {
		return 100;
	}
}
public class ExtraCheeseDecorator extends ToppingDecorator {

	//Has-A
	BasePizza basePizza;

	ExtraCheeseDecorator(BasePizza pizza) {
		this.basePizza = pizza;
	}

	@Override
	int cost() {
		return this.basePizza.cost() + 10;
	}
}
public class ExtraMushroomDecorator extends ToppingDecorator {
	BasePizza basePizza;

	ExtraMushroomDecorator(BasePizza basePizza) {
		this.basePizza = basePizza;
	}

	@Override
	int cost() {
		return basePizza.cost() + 15;
	}
}

Now, time for Pizza Shop 🎉

public class PizzaShop {

	public static void main(String[] args) {
		BasePizza pizza = //code to parent
			new ExtraMushroomDecorator( // topping-2 returns base pizza
				new ExtraCheeseDecorator( // topping-1 returns base pizza
					new FarmhousePizza() // base pizza
				)
			);

		int cost = pizza.cost();
		System.out.println("The cost of pizza :" + cost);

		BasePizza anotherPizza = //code to parent
			new ExtraMushroomDecorator( // topping-2 returns base pizza
				new ExtraCheeseDecorator( // topping-1 returns base pizza
					new MargheritaPizza() // base pizza
				)
			);

		int cost2 = anotherPizza.cost();
		System.out.println("The cost of pizza :" + cost2);
	}
}

output

The cost of pizza :225
The cost of pizza :125

Now, let's understand the advantage and disadvantage of this pattern.

The classic example in JDK is Java I/O stream classes—specifically, classes like:

  • java.io.BufferedInputStream

  • java.io.BufferedOutputStream

  • java.io.DataInputStream

All of these classes extend FilterInputStream or FilterOutputStream, which are abstract decorators. They “decorate” the basic InputStream or OutputStream with additional capabilities (like buffering, pushback, line numbering, etc.), and can be chained together in any order.

InputStream input = new BufferedInputStream(
                        new DataInputStream(
                            new FileInputStream("file.txt")));

Advantages of Decorator Pattern:

  • Flexible and Extensible: You can add new functionalities to objects at runtime, without changing their original class.

  • Follows Open/Closed Principle: Classes are open for extension but closed for modification, so you keep base logic untouched.

  • Easy to Mix and Match: You can combine several decorators in any order to create complex behavior.

  • Reduces Code Duplication: Decorators can be reused for different combinations, so you don’t need to write many subclasses.

Disadvantages of Decorator Pattern:

  • Many Small Classes: Using multiple decorators can lead to a lot of small, similar classes, which may be hard to manage.

  • Complexity: The structure can become complex as you stack several decorators, making debugging and understanding difficult.

  • Order Dependency: The order in which decorators are added can affect the behavior and result, so you need to be careful when layering.

Hope you enjoyed the pattern and the pizza 🎉

Last updated

Was this helpful?