Java programming language logo

Decorator Design Pattern

The Decorator pattern modifies the behavior of an object by wrapping it into another object. The wrapper class uses the functionality of the wrapped class, but modifies or extends it.

This pattern enables to modify object’s behavior at run-time. It also offers a flexible alternative to inheritance. It can be used when subclassing is not an option. Inheritance also used for extending functionality.

To decorate an object means wrapping an object into another. To apply this pattern, the decorated object and decorator object have to implement the same interface. This enables to use a decorator instance where a type of the not decorated class expected. The decorator has a reference to the decorated object and calls its methods, but it modifies or extends them. A decorator object also can be decorated and so on.

Decorator pattern is a structural pattern.

Usage of this pattern can lead to many small objects in our design.

A very common usage of the Decorator pattern is found in the java.io. package.

BufferedReader in = new BufferedReader(new FileReader("some_file.txt"))

In the above code, a stream is created and wrapped by a buffered class that modifies it.

Example

The next Java example is about building a car. First the type is chosen, that is small city car. After that, some options are added to this base version like alloy wheels, LED lights and custom color, therefore its specification and its price are changing. So the car gets altered, decorated by the options. The default car can become a car with bigger alloy wheels and with LED lights car, but it remains a car.

Let’s create an abstract class for the Car. It defines the car’s specification and price.


package com.programcodex.designpatterns.decorator;

public abstract class Car {

    private String specification;
    
    public String getSpecification() {
        return specification;
    }

    public void setSpecification(String specification) {
        this.specification = specification;
    }

    public abstract int getPrice();
}

Next, create a class that gives implementation for the abstract method. Now the chosen model is a CityCar that extends the Car class.


package com.programcodex.designpatterns.decorator;

public class CityCar extends Car {

    public CityCar() {
        setSpecification("A small city car. ($8,000)");
    }
    
    @Override
    public int getPrice() {
        return 8_000;
    }
}

The car is chosen, but we need to add some options like a nicer color, bigger alloys and LED lights. These things are the decorators. They also have an abstract parent class, the OptionDecorator. This extends the Car class and has a reference to the wrapped object in its Car field.


package com.programcodex.designpatterns.decorator;

public abstract class OptionDecorator extends Car {

    protected Car car;

    public OptionDecorator(Car car) {
        this.car = car;
    }

    @Override
    public abstract String getSpecification();

    @Override
    public abstract int getPrice();
}

Now comes the options’ classes. They all extend the OptionDecorator class. First is the ColorOption, but the order of options doesn’t matter.

The getSpecification and the getPrice methods call the same methods of the decorated object and add some extra information to them.


package com.programcodex.designpatterns.decorator;

public class ColorOption extends OptionDecorator {

    private String color;

    public ColorOption(Car car, String color) {
        super(car);
        this.color = color;
    }

    @Override
    public String getSpecification() {
        return car.getSpecification() + "\nCustom color is " + color + " ($1,000)";
    }

    @Override
    public int getPrice() {
        return car.getPrice() + 1000;
    }
}

Next is the AlloysOption class, which has basically the same logic as the ColorOption.


package com.programcodex.designpatterns.decorator;

public class AlloysOption extends OptionDecorator {

    public AlloysOption(Car car) {
        super(car);
    }

    @Override
    public String getSpecification() {
        return car.getSpecification() + "\nBigger alloys. ($1,500)";
    }

    @Override
    public int getPrice() {
        return car.getPrice() + 1500;
    }
}

The last option is the LEDLightsOption class.


package com.programcodex.designpatterns.decorator;

public class LEDLightsOption extends OptionDecorator {

    public LEDLightsOption(Car car) {
        super(car);
    }

    @Override
    public String getSpecification() {
        return car.getSpecification() + "\nLED lights. ($1,500)";
    }

    @Override
    public int getPrice() {
        return car.getPrice() + 1500;
    }
}

And we are done. It’s time to run these things.

The essence of this example is found in the main method. A Car is instantiated and printed out by the printCar method. After that, the Car object is decorated by ColorOption class and printed out again. Finally, it is wrapped by the remaining two decorator classes. The modified car is printed out after every step, but it could have been decorated in one step. For example:

new LEDLightsOption(new AlloysOption(new CityCar()))

As you can see, the car’s specification getting longer and longer after every modification, and of course its price also increments.

Also worth to mention, the printCar method accepts a Car instance. This is not a problem because the decorated instances have the same type as the default one.


package com.programcodex.designpatterns.decorator;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;

public class TestDecorator {
    
    private static final DecimalFormat df;
    
    static {
        DecimalFormatSymbols sym = new DecimalFormatSymbols();
        sym.setDecimalSeparator('.');
        sym.setGroupingSeparator(',');
        df = new DecimalFormat("$###,###.##");
        df.setDecimalFormatSymbols(sym);
    }

    public static void main(String[] args) {
        Car car = new CityCar();
        printCar(car);
        
        car = new ColorOption(car, "Gray");
        printCar(car);
        
        car = new AlloysOption(car);
        printCar(car);
        
        car = new LEDLightsOption(car);
        printCar(car);
    }
    
    private static void printCar(Car car) {
        System.out.println('\n' + car.getSpecification());
        System.out.println("Total: " + df.format(car.getPrice()));
    }
}

The output of the above test class:

The Decorator Design Pattern