Decorator pattern is a structural pattern which adds new functionality to an existing object without modifying its original structure. This tutorial explains decorator pattern with practical examples in Java code.

What is Decorator Pattern?

Decorator pattern is used to dynamically add behavior to existing objects. It’s like wrapping a gift item into multiple boxes and/or decorative ribbons without changing the gift itself. The main feature of this pattern is that it does not change the original object. For example, if you had a Car and you wanted to add several different features such as car with hybrid engine or car with gas engine or may be a flying car? We can add those features by extending the original Car class but then we might need HybridCar with manual gears or HybridCar with automatic gears. So, if we try to create these using subclasses, we might have to create so many subclasses for each simple variation. Also, this kind of inheritance structure is very inflexible. Each subclass can have only one parent as most languages do not support multiple inheritance.

It is better to use composition to handle these scenarios. With composition, we can delegate the work to another object reference. With composition, an object can use various behavior of different classes by using their objects and delegating them all kinds of work. We can wrap the original object in different kinds of wrapper to alter its behavior. That’s why this pattern is also called wrapper pattern. A wrapper is an object that can be connected with some other target object. It has same set of methods as the target object, however, wrapper may also alter the result by doing something before or after it passes the client request to the target object. This pattern uses both inheritance and composition.

This pattern is useful when it’s awkward or impossible to extend an object’s behavior using inheritance. For example, if the superclass has defined class with final keyword, it cannot be extended to add new functionalities. In this case, the only way to extend the behavior would be to wrap this object in another object using decorator pattern.

How to implement Decorator Pattern?

Let’s say we want to implement PaintApp. That app can draw different shapes. Now, on top of drawing these shapes, we can apply different visual efffects like borders or fill the shape with different colors. We can apply these extra effects using Decorator pattern.

We have a Shape interface which defines the draw() method.

1public interface Shape {
2    void draw();
3}

We also have concrete implementation of this Shape interface which have their own drawing logic.

1public class Circle implements Shape {
2    @Override
3    public void draw() {
4        System.out.println("Drawing a circle");
5    }
6}

We can have more than one shape implementing this common interface.

1public class Square implements Shape {
2    @Override
3    public void draw() {
4        System.out.println("Drawing a square");
5    }
6}

Let’s say we want to have shapes with colors and shapes with borders or which are filled with different number of dots. In this case, we can define a ShapeDecorator abstract class. This class will have an instance of Shape which can be extended with different concrete ShapeDecorators. Notice that it contains Shape instance and delegates the draw() method to respective decorator which can add additional functionality.

 1public abstract class ShapeDecorator implements Shape {
 2    private Shape decoaratedShape;
 3
 4    public ShapeDecorator(Shape decoratedShape) {
 5        this.decoaratedShape = decoratedShape;
 6    }
 7
 8    @Override
 9    public void draw() {
10        decoaratedShape.draw();
11    }
12}

Now, we have conrete ShapeDecorator which can fill color into any shape.

 1public class ColorShapeDecorator extends ShapeDecorator {
 2    private final String color;
 3
 4    public ColorShapeDecorator(Shape decoratedShape, String color) {
 5        super(decoratedShape);
 6        this.color = color;
 7    }
 8
 9    @Override
10    public void draw() {
11        super.draw();
12        System.out.println(" with Color: " + color);
13    }
14}

We can also have decorator which can create dots inside the shape of different colors.

 1public class DottedShapeDecorator extends ShapeDecorator {
 2    private final int dots;
 3    private final String dotColor;
 4
 5    public DottedShapeDecorator(Shape decoratedShape, int dots, String dotColor) {
 6        super(decoratedShape);
 7        this.dots = dots;
 8        this.dotColor = dotColor;
 9    }
10
11    @Override
12    public void draw() {
13        super.draw();
14        System.out.println(" with " + dotColor + " colored " + dots + " dots");
15    }
16}

With these constructs, we can create shape with different color and dots.

 1public class Main {
 2    public static void main(String[] args) {
 3        Shape circle = new Circle();
 4
 5        Shape blueCircle = new ColorShapeDecorator(circle, "blue");
 6        blueCircle.draw();
 7
 8        Shape dottedBlueCircle = new DottedShapeDecorator(blueCircle, 100, "red");
 9        dottedBlueCircle.draw();
10    }
11}

This pattern consists of following main parts.

  • Component: It is the common interface for both wrapped object and wrapper object. It is actually the object which needs different variants. In example above, Shape is a component.
  • Concrete Components: These are concrete instances which can be used. In our case, Circle and Square.
  • Base Decorator: This is base decorator which has instance of the component. It is ShapeDecorator in above example. The base decorator delegates operations to the wrapped object.
  • Concrete Decorators: These are actual decorators which add some new functionality. They override methods of the base decorator.
  1. We already have Component and Concrete Comopnents and we want to extend it by adding new behavior
  2. Define the base decorator with commom methods. It should have a reference to the wrapped object.
  3. Create concrete decorators based on the new functionalities needed.

Example: Sandwich Decorator

1public interface Sandwich {
2    public String getDescription();
3    public double getPrice();
4}
 1public class SimpleCheeseSandwich implements Sandwich {
 2    @Override
 3    public String getDescription() {
 4        return "Simple Sandwich";
 5    }
 6
 7    @Override
 8    public double getPrice() {
 9        return 2.49;
10    }
11}

Base decorator for Sandwich

 1public abstract class SandwichDecorator implements Sandwich {
 2    private Sandwich sandwich;
 3
 4    public SandwichDecorator(Sandwich sandwich) {
 5        this.sandwich = sandwich;
 6    }
 7
 8    @Override
 9    public String getDescription() {
10        return sandwich.getDescription() + " with " + getExtraItem();
11    }
12
13    @Override
14    public double getPrice() {
15        return sandwich.getPrice() + getExtraItemPrice();
16    }
17
18    public abstract String getExtraItem();
19    public abstract double getExtraItemPrice();
20}
 1public class ExtraCheeseDecorator extends SandwichDecorator {
 2    public ExtraCheeseDecorator(Sandwich sandwich) {
 3        super(sandwich);
 4    }
 5
 6    @Override
 7    public String getExtraItem() {
 8        return ", Double Cheese ";
 9    }
10
11    @Override
12    public double getExtraItemPrice() {
13        return 1.0d;
14    }
15}
 1public class VegetableDecorator extends SandwichDecorator {
 2    private final String vegetableName;
 3    private final double price;
 4
 5    public VegetableDecorator(Sandwich sandwich, String vegetableName, double price) {
 6        super(sandwich);
 7        this.vegetableName = vegetableName;
 8        this.price = price;
 9    }
10
11    public String getVegetableName() {
12        return vegetableName;
13    }
14
15    public double getVegetablePrice() {
16        return price;
17    }
18
19    @Override
20    public String getExtraItem() {
21        return ", " + getVegetableName();
22    }
23
24    @Override
25    public double getExtraItemPrice() {
26        return getVegetablePrice();
27    }
28}
 1public class MeatDecorator extends SandwichDecorator {
 2    public MeatDecorator(Sandwich sandwich) {
 3        super(sandwich);
 4    }
 5
 6    @Override
 7    public String getExtraItem() {
 8        return ", Meat ";
 9    }
10
11    @Override
12    public double getExtraItemPrice() {
13        return 1.99;
14    }
15}
 1public class Client {
 2    public static void main(String[] args) {
 3        Sandwich sandwich = new SimpleCheeseSandwich();
 4        System.out.println(sandwich.getDescription() + " costs $" + sandwich.getPrice());
 5
 6        sandwich = new ExtraCheeseDecorator(sandwich);
 7        System.out.println(sandwich.getDescription() + " costs $" + sandwich.getPrice());
 8
 9        sandwich = new VegetableDecorator(sandwich, "tomato", 0.99);
10        System.out.println(sandwich.getDescription() + " costs $" + sandwich.getPrice());
11
12        sandwich = new VegetableDecorator(sandwich, "lettuce", 0.49);
13        System.out.println(sandwich.getDescription() + " costs $" + sandwich.getPrice());
14    }
15}

Advantages:

  • This allows you to add extra functionalities to objects at runtime, making the code more flexible.
  • This pattern does not alter the original object. The datatype is still the same with core functionalities.
  • It allows you to add new functionalities using decoarators without modifying the existing class. Thus, following Open/Closed Principle.
  • Each decorator follows Single Responsibility principle promoting clean maintainable code.

Disadvantages:

  • Decorators cannot modify the original wrapped object. So, it has limited control over it.
  • It can result in performance overhead as we have to create and manage many decorator objects.

Summary

  • This pattern is useful when we want to add simple functionalities to original object.
  • It does not alter the original object.
  • It uses both inheritance and composition.