Blog Design Pattern

Decorator Design Pattern and its Building Blocks 🧱 in Java ☕️| Structural Design Pattern

What is the Decorator Design Pattern? 🤔

The Decorator Design Pattern is a structural design pattern that allows us to add new functionality to an existing object without altering its structure. This is achieved by wrapping the original object with an object of a decorator class which provides the additional functionality.

It allows us to dynamically add new behaviours or responsibilities to an object at runtime without modifying its core implementation. By embracing the Decorator Pattern, developers can create highly adaptable and maintainable code, adhering to the Open/Closed Principle. 🔓

Why the Decorator Pattern is considered a Structural Design Pattern?

To understand why it falls under the Structural Design Pattern category, let’s first define what a Structural Design Pattern is:

Structural Design Patterns are concerned with how classes and objects are composed to form larger structures. They help in understanding and facilitating the design by identifying ways to realize relationships between entities. In other words, these patterns focus on the composition of classes and objects to create complex structures that work together to achieve desired functionalities.

The basic definition of a Structural Design Pattern is:

“A Structural Design Pattern is a way of composing classes and objects to form larger structures while keeping these structures flexible and efficient.”

Now, let’s understand why the Decorator Pattern is considered a Structural Design Pattern:

The Decorator Pattern allows you to dynamically attach additional responsibilities or behaviours to an object by wrapping it with one or more decorator objects. This is done by creating a chain of decorator objects that encapsulate the original object, where each decorator object extends the functionality of the previous one.

In essence, the Decorator Pattern is all about creating a flexible and extensible structure of objects by composing them together. It achieves this by:

  1. Object Composition: The Decorator Pattern uses object composition to add or remove responsibilities dynamically. The decorator objects wrap the original object, creating a chain of objects that work together to provide the desired functionality.
  2. Flexibility and Extensibility: By using composition instead of inheritance, the Decorator Pattern allows for more flexibility and extensibility. New decorators can be added or removed without modifying the original object or existing decorators, following the Open/Closed Principle.
  3. Structural Changes at Runtime: The Decorator Pattern enables structural changes to objects at runtime by dynamically adding or removing decorators, effectively changing the object’s behaviour and responsibilities.

These characteristics align with the fundamental goal of Structural Design Patterns, which is to provide ways to compose objects into larger structures while maintaining flexibility and efficiency.

In summary, the Decorator Design Pattern is considered a Structural Design Pattern because it focuses on the composition and organization of objects to create larger, more complex structures. It provides a flexible and extensible way to add or remove responsibilities from objects by wrapping them with decorator objects, allowing for dynamic structural changes at runtime.

Explain Decorator Design Pattern to a 5-year-old kid 🧒🎈📚:

Explain Decorator Design Pattern to a 5-year-old kid

Image from https://limewire.com/

Explaining the Decorator Design Pattern to a 5-year-old can be a bit challenging, but let’s try to make it as simple and relatable as possible. 🤗

Imagine you have a toy robot, and it can walk and make sounds. 🚶‍♂️ That’s the basic functionality of the robot, right? But what if you want to add more features to your robot, like making it dance or shoot little foam balls? 💃🎉

Instead of creating a completely new robot with all those additional features, you can use “decorators” to enhance your existing robot. 🎁

Decorators are like special accessories or costumes that you can put on your robot to give it new abilities. For example, you could put on a “dancing decorator” costume that makes your robot dance whenever you press a button. 💃

Or, you could add a “shooter decorator” accessory that allows your robot to shoot foam balls. 🔫

The best part is that you can mix and match these decorators however you want! 🤖👯‍♂️ You can put on the dancing decorator and the shooter decorator at the same time, making your robot both dance and shoot foam balls.

And if you ever want to remove a decorator, you can simply take it off without affecting the robot’s original walking and sound-making abilities. 👍

Just like how you can customize your toy robot by adding or removing decorators, software developers use the Decorator Design Pattern to add or remove features from objects in their programs without modifying the original code. 💻

This way, they can create flexible and extensible software that can easily adapt to new requirements or changes, just like how you can keep customizing your toy robot with different decorators. 🙌

The Building Blocks: 🧱

  1. Component Interface: The foundation of the Decorator Pattern lies in the Component interface (or abstract class). This interface defines the common methods that both concrete components and decorators must implement. It serves as a contract, ensuring that all objects implementing this interface can be treated uniformly. 🤝
Java
public interface Robot {
    void walk();
    void makeSound();
}

Here The Robot interface defines the common methods that both the concrete robot and the decorators must implement.

  1. Concrete Component: The ConcreteComponent class is a direct implementation of the Component interface. It represents the core functionality that we want to extend or enhance using decorators. This class serves as the base upon which we’ll build our decorated objects. 💎
Java
public class BasicRobot implements Robot {
    @Override
    public void walk() {
        System.out.println("The robot is walking.");
    }

    @Override
    public void makeSound() {
        System.out.println("The robot is making a sound.");
    }
}

The BasicRobot class is a concrete implementation of the Robot interface, representing the core functionality of walking and making sounds.

  1. Decorator Base Class: The Decorator class is an abstract class (or interface) that implements the Component interface. 🎭 It holds a reference to a Component object, which can be either a concrete component or another decorator. This reference allows the decorator to delegate method calls to the wrapped component. The Decorator class also provides a default implementation for the methods defined in the Component interface.
Java
public abstract class RobotDecorator implements Robot {
    protected Robot robot;

    public RobotDecorator(Robot robot) {
        this.robot = robot;
    }

    @Override
    public void walk() {
        robot.walk();
    }

    @Override
    public void makeSound() {
        robot.makeSound();
    }
}

The RobotDecorator class is an abstract class that implements the Robot interface and holds a reference to a Robot object, which can be either a concrete robot or another decorator. It delegates the walk() and makeSound() methods to the wrapped Robot object.

The Decorator class is marked as abstract because we typically don’t want to instantiate it directly. Instead, we create concrete subclasses that extend this class and provide the desired additional behaviour. 🚫

  1. Concrete Decorators: Concrete decorators are subclasses of the Decorator class. They implement the specific behaviour that we want to add to the component. Each concrete decorator overrides the methods defined in the Component interface, calling the superclass’s method (super.operation()) and then performing additional operations before or after the superclass’s method. 💫
Java
public class DancingDecorator extends RobotDecorator {
    public DancingDecorator(Robot robot) {
        super(robot);
    }

    @Override
    public void walk() {
        super.walk();
        System.out.println("The robot is dancing while walking!");
    }

    public void dance() {
        System.out.println("The robot is dancing!");
    }
}

public class ShooterDecorator extends RobotDecorator {
    public ShooterDecorator(Robot robot) {
        super(robot);
    }

    @Override
    public void makeSound() {
        super.makeSound();
        System.out.println("The robot is shooting foam balls!");
    }

    public void shoot() {
        System.out.println("Pew! Pew! The robot is shooting foam balls!");
    }
}

The DancingDecorator and ShooterDecorator classes are concrete decorators that extend the RobotDecorator class. They override the walk() and makeSound() methods to add their behaviour before or after calling the superclass’s method. They also provide additional methods dance() and shoot() to add new functionality to the robot.

The super(component) call in the constructor is crucial. 🔑 It passes the Component object that we want to decorate to the superclass constructor (Decorator). This allows the decorator to hold a reference to the component it is decorating, enabling the decorator to call the component’s methods and add its own behavior before or after those method calls.

  1. Usage and Power of Composition: 🧮 The true power of the Decorator Pattern lies in its ability to compose objects dynamically at runtime. By combining multiple decorators with a concrete component, we can create complex objects with rich functionality. 🌈
Java
public class RobotDemo {
    public static void main(String[] args) {
        Robot basicRobot = new BasicRobot();
        basicRobot.walk();
        basicRobot.makeSound();
        System.out.println();

        Robot dancingRobot = new DancingDecorator(basicRobot);
        dancingRobot.walk();
        dancingRobot.makeSound();
        ((DancingDecorator) dancingRobot).dance();
        System.out.println();

        Robot shootingRobot = new ShooterDecorator(basicRobot);
        shootingRobot.walk();
        shootingRobot.makeSound();
        ((ShooterDecorator) shootingRobot).shoot();
        System.out.println();

        Robot dancingShootingRobot = new DancingDecorator(new ShooterDecorator(basicRobot));
        dancingShootingRobot.walk();
        dancingShootingRobot.makeSound();
        ((DancingDecorator) dancingShootingRobot).dance();
        ((ShooterDecorator) dancingShootingRobot).shoot();
    }
}

In the main method, we create instances of the BasicRobot, DancingDecorator, ShooterDecorator, and a combination of both decorators. We demonstrate how the decorators add new behaviours to the robot without modifying its core implementation.

Real-World Scenarios: 🌍

The Decorator Pattern finds applications in various domains, enabling developers to enhance the functionality of objects in a modular and extensible manner. Here are a few real-world scenarios where the Decorator Pattern shines:

  1. GUI Components: In graphical user interface (GUI) libraries, decorators can be used to add visual or behavioural features to GUI components like windows, buttons, menus, and more. For example, you could have a BorderDecorator that adds a border to a window, a ScrollDecorator that adds scrolling behavior to a text area, or a ToolTipDecorator that adds tooltip functionality to a button. 🖥️
  2. Logging and Instrumentation: Decorators can be employed to add logging or instrumentation capabilities to existing classes without modifying their code. For instance, you could have a LoggingDecorator that logs method calls and parameters, or a PerformanceDecorator that measures the execution time of method invocations. 🔍
  3. Data Compression and Encryption: The Decorator Pattern can be used to add compression or encryption functionality to data streams or network communication. For example, you could have a CompressionDecorator that compresses data before sending it over the network, or an EncryptionDecorator that encrypts sensitive data before storing it. 🔒
  4. Caching: Decorators can be utilized to add caching mechanisms to existing classes. You could have a CachingDecorator that caches the results of expensive computations or database queries, improving performance by returning cached results whenever possible. ⚡
  5. E-commerce Pricing: In an e-commerce application, decorators can be used to dynamically add or alter pricing features to a product. For example, you could have a TaxDecorator that adds tax to the base price of a product, or a DiscountDecorator that applies a discount to the price. 💰

Simple Example

Let’s start with a straightforward example everyone can understand – customizing a coffee order!

Java Code:

Java
// The Component
public interface Coffee {
    String getDescription();
    double cost();
}

// Concrete Component
public class BasicCoffee implements Coffee {
    public String getDescription() {
        return "Basic Coffee";
    }

    public double cost() {
        return 1.00;
    }
}

// Decorator
public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    public double cost() {
        return decoratedCoffee.cost();
    }
}

// Concrete Decorators
public class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", with milk";
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost() + 0.5;
    }
}

public class WithSugar extends CoffeeDecorator {
    public WithSugar(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", with sugar";
    }

    @Override
    public double cost() {
        return decoratedCoffee.cost() + 0.2;
    }
}

Using the Decorators:

Java

public class CoffeeShop {
    public static void main(String[] args) {
        Coffee myOrder = new BasicCoffee();
        System.out.println(myOrder.getDescription() 
                           + " $" + myOrder.cost());

        myOrder = new WithMilk(myOrder);
        System.out.println(myOrder.getDescription() 
                           + " $" + myOrder.cost());

        myOrder = new WithSugar(myOrder);
        System.out.println(myOrder.getDescription() 
                           + " $" + myOrder.cost());
    }
}

Output:

Java
Basic Coffee $1.0
Basic Coffee, with milk $1.5
Basic Coffee, with milk, with sugar $1.7

In this example, we have a BasicCoffee object to which we dynamically add “milk” and “sugar” enhancements. Each addition is implemented as a CoffeeDecorator that wraps around the BasicCoffee.

Complex Real-World Example

Now, let’s look at a more complex example, say, in a GUI application where you might want to add features like borders, scrolling, or colour changes to certain elements without changing the original classes of those elements.

Java Code:

Java
// The GUI component we want to decorate
public interface GUIComponent {
    void draw();
}

// A concrete component
public class Window implements GUIComponent {
    public void draw() {
        // Draw the window
    }
}

// Decorator base class
public abstract class ComponentDecorator implements GUIComponent {
    protected GUIComponent component;

    public ComponentDecorator(GUIComponent component) {
        this.component = component;
    }

    public void draw() {
        component.draw(); // Delegating the task
    }
}

// Concrete Decorators
public class BorderDecorator extends ComponentDecorator {
    public BorderDecorator(GUIComponent component) {
        super(component);
    }

    @Override
    public void draw() {
        super.draw();
        drawBorder();
    }

    private void drawBorder() {
        // Draw the border around the component
    }
}

public class ScrollDecorator extends ComponentDecorator {
    public ScrollDecorator(GUIComponent component) {
        super(component);
    }

    @Override
    public void draw() {
        super.draw();
        addScroll();
    }

    private void addScroll() {
        // Add scroll functionality
    }
}

Using the GUI Decorators:

Java
public class GUI {
    public static void main(String[] args) {
        GUIComponent window = new Window();
        window.draw(); // Draw basic window

        window = new BorderDecorator(window);
        window.draw(); // Draw window with border

        window = new ScrollDecorator(window);
        window.draw(); // Draw window with border and scroll
    }
}

Conclusion

The Decorator pattern offers a robust alternative to subclassing for extending behaviour.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over