The Decorator pattern allows you to add new features to classes without modifying their structure.
The concept of this pattern is to dynamically add new behavior or functionality to the main class.
The official definition says that it “allows dynamic adjustment of objects to modify their existing responsibilities and behaviors.”
What are the parts of the Decorator design pattern?
The decorator pattern consists mainly of an Interface from which the particular class is implemented and the decorators which will add more functionality to the particular class.
- Component Interface: is the interface (can be an abstract class too) that defines the functionality and from which the specific class and decorators are inherited.
- Concrete Component: is the main implementation and whose class will receive decorators to add extra functionality dynamically.
- Decorator: It can be an abstract class or not that defines the Decorator inherited from the Component interface and from which all other decorators will be created. The decorator must keep the reference to the original object in order to invoke it and then add other features of the decorator. Each decorator has a relationship with the type HAS-A component (has a).
- Concrete Decorator: are the classes that extend or implement the Decorator with the bounded functionality.
How to create the Decorator design pattern in Java
We said that the concept of the decorator pattern is to add functionalities to the main object dynamically. Thus avoiding the need to create subclasses to the main class to add functionality.
The advantage is that this way we do not affect all classes. Each class defines a specific functionality that is added and decorates to the main class without creating subclasses.
The first thing we need to look for is the common behavior that all objects have and that could be extracted to an interface. This interface will be the contract that all implementations, both the specific class and the decorators must fulfill.
Let me show with an example.
Let us think of a Watch to which functionalities are added and according to the added functionalities it becomes a sports watch or a Luxury watch.
Let’s define the component that will be the Watch interface.
package patterns.decorator;
public interface Watch {
void createFunctionality();
}
Now we create the particular BasicWatch class from the interface
package patterns.decorator;
public class BasicWatch implements Watch {
@Override
public void createFunctionality() {
System.out.println(" Basic Watch with: ");
this.addTimer();
}
private void addTimer() {
System.out.print(" Timer");
}
}
We created the WatchDecorator decorator also from the interface.
package patterns.decorator;
public abstract class WatchDecorator implements Watch {
private final Watch watch;
public WatchDecorator(Watch watch) {
this.watch = watch;
}
@Override
public void createFunctionality() {
this.watch.createFunctionality();
}
}
We create the rest of decorators from the main decorator that will add the particular functionalities.
package patterns.decorator;
public class SportWatchDecorator extends WatchDecorator {
public SportWatchDecorator(Watch watch) {
super(watch);
}
@Override
public void createFunctionality(){
super.createFunctionality();
System.out.print(" and more features (Sport Watch): ");
this.addPedometer();
this.addSleepMode();
}
private void addPedometer() {
System.out.print(" Pedometer");
}
private void addSleepMode() {
System.out.print(" SleepMode ");
}
}
package patterns.decorator;
public class LuxuryWatchDecorator extends WatchDecorator {
public LuxuryWatchDecorator(Watch watch) {
super(watch);
}
@Override
public void createFunctionality() {
super.createFunctionality();
System.out.print(" and more features (Luxury Watch): ");
this.addFastCharge();
this.addImpermeability();
this.addNFC();
}
private void addFastCharge() {
System.out.print(" FastCharge ");
}
private void addImpermeability() {
System.out.print(" Impermeability ");
}
private void addNFC() {
System.out.print(" NFC ");
}
}
Well, now let’s try our decorator pattern.
package patterns.decorator;
public class ClientDecoratorPattern {
public static void main(String... args) {
Watch basicWatch = new BasicWatch();
basicWatch.createFunctionality();
System.out.println("\n---------");
Watch sportsWatch = new SportWatchDecorator(new BasicWatch());
sportsWatch.createFunctionality();
System.out.println("\n---------");
Watch sportsLuxuryWatch = new LuxuryWatchDecorator(new SportWatchDecorator(new BasicWatch()));
sportsLuxuryWatch.createFunctionality();
}
}
What we did in the previous code:
- We created our concrete and main BasicWatch object that does not have much functionality other than the timer.
- We create a SportWatchDecorator decorator object that adds more functionality to the main BasicWatch object
- We created a LuxuryWatchDecorator decorator object that adds more functionality to the SportWatch decorator that in turn adds functionality to the BasicWatch object.
The output:
Basic Watch with:
Timer
---------
Basic Watch with:
Timer and more features (Sport Watch): Pedometer SleepMode
---------
Basic Watch with:
Timer and more features (Sport Watch): Pedometer SleepMode and more features (Luxury Watch): FastCharge Impermeability NFC
Conclusion
We saw how to create a pattern decorator that allows us to add new functionality in cases where extending the main class is not a good option.
This functionality can be added statically or dynamically according to conditions and helps us to define the features in the decorator classes, so it is simpler to implement them.
You can review this code GitHub