3.4. Decorator

Maheshmaddi
4 min readApr 9, 2023

--

The Decorator pattern is a structural design pattern that enables adding new behavior to an object dynamically, without altering its structure. It involves a set of decorator classes that mirror the interface of the object they are extending while adding or overriding behavior. Decorators wrap concrete components, and other decorators can wrap these decorators, forming a chain of responsibility.

The Decorator pattern is typically used when:

  1. You want to add behavior to an object without affecting other instances of the same class.
  2. You want to add or modify behavior of an object dynamically during runtime.
  3. Subclassing is impractical or undesirable due to a large number of possible combinations of functionality.

To implement the Decorator pattern, follow these steps:

  1. Define an interface or abstract class for the component that specifies the common methods.
  2. Create a concrete component class that implements the common interface or extends the abstract class.
  3. Create an abstract decorator class that also implements the common interface or extends the abstract class, and maintains a reference to a component object.
  4. Create concrete decorator classes that extend the abstract decorator class and implement the desired additional behavior or modifications.

Here’s a simple example of the Decorator pattern in Java:

// Component interface
public interface Component {
void operation();
}

// Concrete component
public class ConcreteComponent implements Component {
@Override
public void operation() {
System.out.println("ConcreteComponent operation");
}
}

// Abstract decorator
public abstract class Decorator implements Component {
protected Component component;

public Decorator(Component component) {
this.component = component;
}

@Override
public void operation() {
component.operation();
}
}

// Concrete decorators
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}

@Override
public void operation() {
System.out.println("ConcreteDecoratorA operation");
super.operation();
}
}

public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}

@Override
public void operation() {
System.out.println("ConcreteDecoratorB operation");
super.operation();
}
}

// Client code
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratedComponentA = new ConcreteDecoratorA(component);
Component decoratedComponentB = new ConcreteDecoratorB(decoratedComponentA);

decoratedComponentB.operation(); // Outputs: "ConcreteDecoratorB operation", "ConcreteDecoratorA operation", "ConcreteComponent operation"
}
}

In this example, the Component interface represents the common interface for both concrete components and decorators. The ConcreteComponent class implements the Component interface, while the Decorator abstract class also implements the Component interface and maintains a reference to a component object. The ConcreteDecoratorA and ConcreteDecoratorB classes extend the Decorator abstract class and implement the desired additional behavior.

Advantages of the Decorator pattern:

  1. Flexibility: The Decorator pattern allows adding or modifying behavior of an object dynamically and independently of other instances of the same class.
  2. Supports the Single Responsibility Principle: Instead of having a single class responsible for multiple behaviors, each decorator class can focus on a specific behavior or modification.

Disadvantages of the Decorator pattern:

  1. Increased complexity: The Decorator pattern introduces additional classes and interfaces, which can increase the overall complexity of the code.
  2. Confusing object structure: Due to the chain of responsibility formed by decorators, it might become difficult to understand the object structure and behavior at runtime.

When using the Decorator pattern, consider its benefits and drawbacks carefully. Use the pattern when you need to add or modify the behavior of an object dynamically during runtime, without affecting other instances of the same class, or when subclassing is not a practical solution. Be aware of the potential complexity and confusing object structure that may be introduced by the pattern. As a best practice, ensure that each decorator class has a well-defined responsibility and that the pattern is applied judiciously to maintain a clean and understandable codebase.

Use case: Implementing a Simple Text Editor using the Decorator Pattern

Class Diagram for Simple Text Editor using Decorator Pattern

We will create a simple text editor that supports adding various formatting options (like bold, italics, and underlining) to text using the Decorator Pattern. This will allow us to add or remove formatting options dynamically without modifying the original text.

  1. Create the TextComponent interface:
public interface TextComponent {
String getText();
}

2. Create the concrete component PlainText:

public class PlainText implements TextComponent {
private String text;

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

@Override
public String getText() {
return text;
}
}

3. Create the abstract decorator class TextDecorator:

public abstract class TextDecorator implements TextComponent {
protected TextComponent textComponent;

public TextDecorator(TextComponent textComponent) {
this.textComponent = textComponent;
}

@Override
public String getText() {
return textComponent.getText();
}
}

4. Create concrete decorator classes for various formatting options:

public class BoldTextDecorator extends TextDecorator {
public BoldTextDecorator(TextComponent textComponent) {
super(textComponent);
}

@Override
public String getText() {
return "<b>" + textComponent.getText() + "</b>";
}
}

public class ItalicTextDecorator extends TextDecorator {
public ItalicTextDecorator(TextComponent textComponent) {
super(textComponent);
}

@Override
public String getText() {
return "<i>" + textComponent.getText() + "</i>";
}
}

public class UnderlineTextDecorator extends TextDecorator {
public UnderlineTextDecorator(TextComponent textComponent) {
super(textComponent);
}

@Override
public String getText() {
return "<u>" + textComponent.getText() + "</u>";
}
}

5. Test the implementation:

public class Main {
public static void main(String[] args) {
TextComponent plainText = new PlainText("Hello, World!");
TextComponent boldText = new BoldTextDecorator(plainText);
TextComponent italicBoldText = new ItalicTextDecorator(boldText);
TextComponent underlineItalicBoldText = new UnderlineTextDecorator(italicBoldText);

System.out.println("Plain text: " + plainText.getText());
System.out.println("Bold text: " + boldText.getText());
System.out.println("Italic and bold text: " + italicBoldText.getText());
System.out.println("Underline, italic, and bold text: " + underlineItalicBoldText.getText());
}
}

Output:

Plain text: Hello, World!
Bold text: <b>Hello, World!</b>
Italic and bold text: <i><b>Hello, World!</b></i>
Underline, italic, and bold text: <u><i><b>Hello, World!</b></i></u>

This use case demonstrates how the Decorator Pattern can be used to dynamically add or remove formatting options to text in a simple text editor without modifying the original text.

Note: For complete list of design patterns click here

--

--