Skip to main content
Home / Blog / Decorator Pattern Explained

Decorator Pattern Explained

Brevitaz Team April 24, 2019 3 min

Overview

In recent posts, we identified issues that can arise with class explosion designs. Let's take a quick look at the fundamental design problems that exist.

Existing Class Hierarchy Issues

In a poorly designed hierarchy, toppings of Pizza are hard-coded at compile-time. These are actually concrete entities in the real world. If we want to add new items as toppings, we need to modify the class – a violation of the Open-Closed Principle (OCP). Ideally, we should be able to add any new topping "without modifying the class". As OCP states:

"Classes should be closed for modification and open for enhancement."

The Decorator Pattern Solution

We need the capability to add behavior to an object at runtime and provide the client of the Pizza class greater flexibility. Here is the approach to follow for maximum flexibility in design:

  1. Take a plain Pizza object
  2. Decorate it with Paneer
  3. Decorate it with Olives
  4. Call the cost() method, which will be delegated to "Add-on" or "Decorator" objects to add up their cost

How Cost Calculation Works

For a FreshVeggiePizza with Paneer and Olives, there is an important point: there must be a cost() method in each "Decorator" object as well as in the Pizza class. This is achieved by creating a super class "Decorator" for each topping item. The Food class will have cost() and getDescription() methods. Additionally, you should be able to print the description of the food according to what toppings are added, rather than hardcoding the description into the Pizza class.

Class Design With Decorator Pattern

Recipe of FreshVeggiePizza with Paneer and Olive

  1. Create a classic FreshVeggiePizza
  2. Put a layer of Paneer over it
  3. Spread some olives over the pizza

Your FreshVeggiePizza with paneer and olives is ready. When you print the price of the pizza, it will calculate: cost of FreshVeggiePizza + cost of Paneer + cost of Olive. This provides a fair level of dynamism in object decoration.

Open-Closed Principle Achieved

We don't need to change our Pizza class at all to add toppings that may change the cost. We don't care if the cost of paneer or olive changes, because these attributes are parameterized and "decorated" over pizzas rather than tightly coupled into the pizza class.

Decorators In Practice

To see real-life decorators, look at java.io classes:

  • Replace Food with InputStream
  • Replace Decorator with FilterInputStream
  • Replace FreshVeggiePizza with FileInputStream/ByteArrayInputStream/StringBufferInputStream
  • Replace Paneer with PushbackInputStream/BufferedInputStream/DataInputStream

Summary

The key steps to implement the Decorator Pattern:

  1. Identify properties and methods common to decorators and client components (in our case, the Pizza is the client)
  2. Create a super class for these common methods
  3. Create a Decorator super-class and identify methods which need to be implemented by each decorator
  4. Pass a reference of the client in each decorator to allow the decorator to add its own properties
  5. The job of the decorator is to extend the behavior of the client without modifying existing code

This pattern achieves flexibility and adherence to the Open-Closed Principle while maintaining clean, maintainable code.

BT

Brevitaz Team

A member of the Brevitaz team sharing insights on software engineering, big data, and cloud technologies.

Back to all articles