Extending an objects functionality can be done statically (at compile time) by using inheritance however it might be necessary to extend an objects functionality dynamically (at runtime) as an object is used.
Intent
- Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
- Client-specified embellishment of a core object by recursively wrapping it.
Implementation
Below is an interface depicting an icecream
// Icecream.java
public interface Icecream {
public String makeIcecream();
}
This is the base class on which the decorators will be added.
// SimpleIcecream.java
public class SimpleIcecream implements Icecream {
@Override
public String makeIcecream() {
return "Base Icecream";
}
}
Following class is the decorator class. It is the core of the decorator design pattern. It contains an attribute for the type of interface. Instance is assigned dynamically at the creation of decorator using its constructor. Once assigned that instance method will be invoked.
// IcecreamDecorator.java
abstract class IcecreamDecorator implements Icecream {
protected Icecream specialIcecream;
public IcecreamDecorator(Icecream specialIcecream) {
this.specialIcecream = specialIcecream;
}
public String makeIcecream() {
return specialIcecream.makeIcecream();
}
}
These are two decorators, concrete class implementing the abstract decorator. When the decorator is created the base instance is passed using the constructor and is assigned to the super class.
// NuttyDecorator.java
public class NuttyDecorator extends IcecreamDecorator {
public NuttyDecorator(Icecream specialIcecream) {
super(specialIcecream);
}
public String makeIcecream() {
return specialIcecream.makeIcecream() + addNuts();
}
private String addNuts() {
return " + cruncy nuts";
}
}
// HoneyDecorator.java
public class HoneyDecorator extends IcecreamDecorator {
public HoneyDecorator(Icecream specialIcecream) {
super(specialIcecream);
}
public String makeIcecream() {
return specialIcecream.makeIcecream() + addHoney();
}
private String addHoney() {
return " + sweet honey";
}
}
Execution of the decorator pattern
We can use as many decorators in any order we want. This excellent flexibility and changing the behaviour of an instance of our choice at runtime is the main advantage of the decorator design pattern.
// TestDecorator.java
public class TestDecorator {
public static void main(String args[]) {
Icecream icecream = new HoneyDecorator(new NuttyDecorator(new SimpleIcecream()));
System.out.println(icecream.makeIcecream());
}
}
Output
Base Icecream + cruncy nuts + sweet honey
Decorator Pattern in Java API
The java.io
classes are based on Decorator.
This example also points out one of the downsides of the Decorator Pattern: designs often result in a large number of small classes that can be overwhelming.