Decorator Design Pattern Explained 🔄 | LambdaTest

Hey testers! :face_with_monocle: Ever wondered about the Decorator Design Pattern? :art: Watch our tutorial to unravel the mysteries of this powerful design pattern, explained simply for you. Don’t miss out on elevating your coding skills!

#DesignPattern #CodingTutorial #DecoratorPattern

The Decorator pattern is a clever design strategy that lets you enhance objects with new behaviors by wrapping them in special decorator objects that carry these new features. It’s a flexible way to extend functionality without resorting to subclassing, as decorators can be layered and mixed in numerous combinations to achieve complex functionality.

Benefits:

  1. Enhancement without alteration: You can add new behaviors to objects without changing their underlying structure, keeping their original form intact.
  2. Adherence to open/closed principle: This pattern supports seamless enhancements; you can introduce new functionality without tweaking the existing code, making your application easier to maintain and extend.
  3. Encourages focused responsibilities: Each decorator is tasked with a specific behavior, promoting a cleaner and more organized codebase where each class handles a single aspect, aligning with the single responsibility principle.

I wanted to share a little insight into the Decorator pattern, a design pattern that’s incredibly useful for adding new responsibilities to objects dynamically. It’s a great alternative to subclassing, as it allows you to extend functionality using composition rather than inheritance. This promotes code reusability and maintainability.

To make it more relatable, let’s look at a fun example involving everyone’s favorite—pizza! :pizza: Here’s how you can understand the concept with a Decorator Pattern Example for Pizza Toppings:


interface Pizza {
    String getDescription();
    double getCost();
}

class PlainPizza implements Pizza {
    public String getDescription() {
        return "Plain Pizza";
    }

    public double getCost() {
        return 5.0;
    }
}

abstract class PizzaDecorator implements Pizza {
    protected Pizza decoratedPizza;

    public PizzaDecorator(Pizza decoratedPizza) {
        this.decoratedPizza = decoratedPizza;
    }

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

    public double getCost() {
        return decoratedPizza.getCost();
    }
}

class CheeseDecorator extends PizzaDecorator {
    public CheeseDecorator(Pizza decoratedPizza) {
        super(decoratedPizza);
    }

    public String getDescription() {
        return super.getDescription() + ", with Cheese";
    }

    public double getCost() {
        return super.getCost() + 1.0;
    }
}

public class DecoratorPatternPizzaExample {
    public static void main(String[] args) {
        Pizza pizza = new PlainPizza();
        System.out.println(pizza.getDescription() + " costs $" + pizza.getCost());

        pizza = new CheeseDecorator(pizza);
        System.out.println(pizza.getDescription() + " costs $" + pizza.getCost());
    }
}

In this example, we start with a basic PlainPizza and then use the CheeseDecorator to add cheese as a topping. Notice how the pizza’s description and cost are updated dynamically without changing the original PlainPizza class. This approach makes it easy to add as many toppings as you like, following the same pattern.

Consider a software application that allows users to customize the appearance of text in a document. Instead of creating subclasses for each text style (e.g., bold, italic, underline), the Decorator pattern can be used.

The base text object represents plain text, and decorators can be used to add styles such as bold, italic, or underline dynamically. This approach allows users to combine different styles and easily add or remove them without modifying the base text class.

Below is an example.


interface Text {
    String getContent();
}

class PlainText implements Text {
    private String content;

    public PlainText(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

abstract class TextDecorator implements Text {
    protected Text decoratedText;

    public TextDecorator(Text decoratedText) {
        this.decoratedText = decoratedText;
    }

    public String getContent() {
        return decoratedText.getContent();
    }
}

class BoldDecorator extends TextDecorator {
    public BoldDecorator(Text decoratedText) {
        super(decoratedText);
    }

    public String getContent() {
        return "<b>" + super.getContent() + "</b>";
    }
}

public class TextEditor {
    public static void main(String[] args) {
        Text text = new PlainText("Hello, world!");
        System.out.println("Plain Text: " + text.getContent());

        // Decorate with bold
        text = new BoldDecorator(text);
        System.out.println("Bold Text: " + text.getContent());
    }
}

In this example, Text is the interface representing text objects, PlainText is a concrete implementation of Text, TextDecorator is the base decorator class, and BoldDecorator is a concrete decorator that adds bold styling to text.