Decorator design pattern in Java and Python

Structural Design Patterns provide different ways to create a class structure, for example using inheritance and composition to create a large object from small objects. Following design patterns come under this category.

Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.

The Decorator pattern applies when there is a need to dynamically add as well as remove responsibilities to a class, and when subclassing would be impossible due to the large number of subclasses that could result. Decorators provide a alternative to subclassing for extending functionality.

The Decorator pattern is used to extend the functionality of an object dynamically without having to change the original class source or using inheritance. This is accomplished by creating an object wrapper referred to as a Decorator around the actual object.

Extending an object's functionality can be done statically (at compile time) by using inheritance however it might be necessary to extend an object's functionality dynamically (at runtime) as an object is used.

The Decorator object is designed to have the same interface as the underlying object. This allows a client object to interact with the Decorator object in exactly the same manner as it would with the underlying actual object. The Decorator object contains a reference to the actual object. The Decorator object receives all requests (calls) from a client. In turn, it forwards these calls to the underlying object. The Decorator object adds some additional functionality before or after forwarding requests to the underlying object. This ensures that the additional functionality can be added to a given object externally at runtime without modifying its structure.

The figure below shows a UML class diagram for the Decorator pattern:

dp_decorator.png

The participants classes in the Decorator pattern are:

  • Component. Interface for objects that can have responsibilities added to them dynamically.
  • Concrete component. Defines an object to which additional responsibilities can be added.
  • Decorator. Maintains a reference to a Component object and defines an interface that conforms to Component's interface.
  • Concrete decorators. Concrete Decorators extend the functionality of the component by adding state or adding behavior.

When to use Decorator pattern:

  • To add responsibilities to individual objects dynamically without affecting other objects.
  • For responsibilities that can be withdrawn.
  • When extension by subclassing is impractical.
  • It’s easy to maintain and extend when the number of choices are more.

Related patterns:

  • Adapter pattern. A decorator is different from an adapter in that a decorator changes object's responsibilities, while an adapter changes an object interface.
  • Composite pattern. A decorator can be viewed as a degenerate composite with only one component. However, a decorator adds additional responsibilities.

Java

Suppose we want to implement different kinds of cars – we can create interface Car to define the assemble method and then we can have a Basic car, further more we can extend it to Sports car and Luxury car.

But if we want to get a Car at runtime that has both the features of Sports car and Luxury car, then the implementation gets complex and if further more we want to specify which features should be added first, it gets even more complex. Now imagine if we have ten different kind of cars, the implementation logic using inheritance and composition will be impossible to manage. To solve this kind of programming situation, we apply decorator pattern in Java.

We need to have following types to implement Decorator design pattern.

Component interface. The interface or abstract class defining the methods that will be implemented. In our case Car will be the component interface.

public interface Car {
    public void assemble();
}

Concrete component. The basic implementation of the component interface. We can have BasicCar class as our concrete component.

public class BasicCar implements Car {
    @Override
    public void assemble() {
        System.out.print("Basic Car.");
    }
}

Decorator. Decorator class implements the component interface and it has a HAS-A relationship with the component interface. The component variable should be accessible to the child decorator classes, so we will make this variable protected.

public class CarDecorator implements Car {
    protected Car car;

    public CarDecorator(Car c){
        this.car = c;
    }

    @Override
    public void assemble() {
        this.car.assemble();
    }
}

Concrete decorators. Extending the base decorator functionality and modifying the component behavior accordingly. We can have concrete decorator classes as LuxuryCar and SportsCar.

public class SportsCar extends CarDecorator {
    public SportsCar(Car c) {
        super(c);
    }

    @Override
    public void assemble(){
        super.assemble();
        System.out.print("Adding features of Sports Car.");
    }
}
public class LuxuryCar extends CarDecorator {
    public LuxuryCar(Car c) {
        super(c);
    }

    @Override
    public void assemble(){
        super.assemble();
        System.out.print("Adding features of Luxury Car.");
    }
}
public class DecoratorPatternTest {
    public static void main(String[] args) {
        Car sportsCar = new SportsCar(new BasicCar());
        sportsCar.assemble();
        System.out.println("\n-----");

        Car sportsLuxuryCar = new SportsCar(new LuxuryCar(new BasicCar()));
        sportsLuxuryCar.assemble();
    }
}

Python

import abc

class Car(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def assemble(self):
        pass


class BasicCar(Car):
    def assemble(self):
        print("Basic Car.")


class CarDecorator(Car):
    def __init__(self, car):
        self.car = car

    def assemble(self):
        self.car.assemble()


class SportsCar(CarDecorator):
    def __init__(self, car):
        super(SportsCar, self).__init__(car)

    def assemble(self):
        super(SportsCar, self).assemble()
        print("Adding features of Sports Car.")


class LuxuryCar(CarDecorator):
    def __init__(self, car):
        super(LuxuryCar, self).__init__(car)

    def assemble(self):
        super(LuxuryCar, self).assemble()
        print("Adding features of Luxury Car.")


if __name__ == '__main__':
    sports_car = SportsCar(BasicCar())
    sports_car.assemble()
    print("-----")

    sports_luxury_car = SportsCar(LuxuryCar(BasicCar()))
    sports_luxury_car.assemble()
comments powered by Disqus