Skip to content

Extending Functionality Seamlessly with Decorator Design Pattern

Posted on:September 9, 2022 at 03:13 PM

In the realm of design patterns, the Decorator Pattern stands as a straightforward yet incredibly useful tool. Sometimes referred to as a wrapper, this pattern lets us augment the behavior of an object without altering its structure. Essentially, it acts as an extension, enveloping an existing object with new features and functionality. Let’s explore this pattern further using a practical example.

Table of contents

Open Table of contents

Sections

Understanding the Decorator Pattern

The Decorator pattern is your go-to solution when you want to add new behavior to an object but can’t modify its existing structure. Whether the object belongs to a third-party library or is a class in another system, the decorator pattern comes to the rescue. Here’s a quick overview of its advantages:

1. Enhanced Behavior:

It allows for extending an object’s functionality without modifying the original code. This is particularly useful when working with classes from external libraries.

2. Behavior Overriding:

The decorator pattern also permits the override of existing behavior, providing the freedom to tailor an object’s actions according to specific requirements.

3. Non-Invasive Changes:

With the decorator pattern, you can make changes to a class’s behavior without altering its core implementation, promoting non-invasive modifications.

Implementing a Practical Example

Let’s delve into a simple scenario where we enhance a coffee machine’s functionality. Imagine we have an existing coffee machine, and we wish to add features like making a milk coffee without altering the original machine. This is where the decorator pattern comes into play.

Interface defining CoffeeMachine behavior

public interface CoffeeMachine {
    void makeSmallCoffee();
    void makeLargeCoffee();
}

Concrete implementation of CoffeeMachine

public class NormalCoffeeMachine implements CoffeeMachine {
    public void makeSmallCoffee() {
        System.out.println("Normal coffee machine making small coffee.");
    }

    public void makeLargeCoffee() {
        System.out.println("Normal coffee machine making large coffee.");
    }
}

Enhanced CoffeeMachine acting as a decorator

public class EnhancedCoffeeMachine implements CoffeeMachine {
    private final CoffeeMachine machine;

    public EnhancedCoffeeMachine(CoffeeMachine machine) {
        this.machine = machine;
    }

    public void makeSmallCoffee() {
        System.out.println("Enhanced coffee machine making small coffee.");
    }

    public void makeLargeCoffee() {
        machine.makeLargeCoffee();  // Maintain existing behavior
    }

    public void makeMilkCoffee() {
        machine.makeLargeCoffee();
        System.out.println("Enhanced coffee machine adding milk.");
    }
}

Client code to demonstrate the usage

public class Client {
    public static void main(String[] args) {
        CoffeeMachine normalMachine = new NormalCoffeeMachine();
        CoffeeMachine enhancedMachine = new EnhancedCoffeeMachine(normalMachine);

        normalMachine.makeSmallCoffee();
        normalMachine.makeLargeCoffee();

        enhancedMachine.makeSmallCoffee();
        enhancedMachine.makeLargeCoffee();
        enhancedMachine.makeMilkCoffee();
    }
}

Conclusion

The Decorator Design Pattern is a powerful tool when it comes to extending an object’s functionality without changing its structure. By wrapping existing objects with additional features, this pattern aids in maintaining clean and non-intrusive code. Whether you want to override behavior or augment an object’s capabilities, the decorator pattern proves to be a versatile and invaluable tool.

That concludes our exploration of the Decorator Design Pattern. Stay tuned for more insights into design patterns and software development concepts. Happy coding!