This pattern is used to extend the functionality of objects without the usage of inheritance or changing the original code. This is part of the series "Design Pattern in Java".
What is a Decorator?
A Decorater is a design pattern (used in programming in general) to extend the functionality of an object by creating a wrapper object.
This wrapper object which is called a decorator is used to wrap the original object that should be extended.
The decorater object has the same functions as the original object that is wrapped. But you can also extend it with specific functions that are used by this decorator.
The functionality of the decorator object is added at run time of your application.
A UML diagram of an application that uses the design pattern "Decorator".
The interface "Burger": This interface is also called the "Component" and it defines the methods that will be implemented by class that are added afterwards.
The class "ChickenBurger": This class is the implementation of the "Component". There can be several classes that implement the "Component" as well.
The abstract class "BurgerDecorator": This is the decorator that decorates our original Burger object and that contains the "Component" object. The in this class defined instance objects are accessed by the child classes.
The classes "Exotic", "Mozarella" and "Vegetables": These are the concrete decorators. They implement the decoration functionality and can be extended with their respective functionality.
An example application
This is an example application called "TastyBurgers" which is the realization of the UML diagram above.
With the help of this example you will get a more practical understanding of this design pattern.
a. Interface of the Component
public interface Burger {
String getName();
Integer getToppingsAmount();
}
All the methods that are at least available for a Burger are defined here.
b. Implementation of the Component
public class ChickenBurger implements Burger {
public ChickenBurger() {
}
@Override
public String getName() {
return "Standard Chicken Burger";
}
@Override
public Integer getToppingsAmount() {
return 0;
}
}
c. Decorator class
public abstract class BurgerDecorator implements Burger{
protected Burger burger;
public BurgerDecorator(Burger b) {
this.burger=b;
}
@Override
public String getName() {
return burger.getName();
}
@Override
public Integer getToppingsAmount() {
return burger.getToppingsAmount();
}
}
This abstract class implements the defined interface of a Burger.
d. Concrete Decorators
public class Exotic extends BurgerDecorator{
public Exotic(Burger b) {
super(b);
}
@Override
public String getName() {
return "Exotic Flavour Edition";
}
@Override
public Integer getToppingsAmount() {
return 5;
}
//Extended functionality - Only available in an Exotic Burger
public Integer getSpicinessLevel() {
return 10;
}
}
public class Mozarella extends BurgerDecorator{
public Mozarella(Burger b) {
super(b);
}
@Override
public String getName() {
return "Mozarella Cheese";
}
@Override
public Integer getToppingsAmount() {
return 2;
}
}
public class Vegetables extends BurgerDecorator {
public Vegetables(Burger b) {
super(b);
}
@Override
public String getName() {
return "Healthy Vegetables Edition";
}
@Override
public Integer getToppingsAmount() {
return 4;
}
}
The functionality extentions are added here.
The created decoraters can be called in this format:
public class TastyBurgers {
public static void main(String[] args) {
Burger burgerA = new Mozarella(new ChickenBurger());
Burger burgerB = new Exotic(new ChickenBurger());
Burger burgerC = new Vegetables(new ChickenBurger());
System.out.println("Burger A Name: " + burgerA.getName() + " - Toppings No.: " + burgerA.getToppingsAmount());
System.out.println("Burger B Name: " + burgerB.getName() + " - Toppings No.: " + burgerB.getToppingsAmount());
System.out.println("Burger C Name: " + burgerC.getName() + " - Toppings No.: " + burgerC.getToppingsAmount());
//The function which was extended (class: Exotic) can be accessed only if you define an object directly with the class name "Exotic":
Exotic burgerBExtended = new Exotic(new ChickenBurger());
System.out.println("Burger B Extended - Name: " + burgerBExtended.getName() + " - Toppings No.: " + burgerBExtended.getToppingsAmount() + " - Spiciness Level: " + burgerBExtended.getSpicinessLevel());
}
}
Output:
Burger A Name: Mozarella Cheese - Toppings No.: 2
Burger B Name: Exotic Flavour Edition - Toppings No.: 5
Burger C Name: Healthy Vegetables Edition - Toppings No.: 4
Burger B Extended - Name: Exotic Flavour Edition - Toppings No.: 5 - Spiciness Level: 10
If you want to use an extended functionality in a concrete decorator, then you have to the declare the object directly without the interface class.
This was an example of a decorator. If you program in Java, then you will find regularly Java classes that use the decorater pattern. For example BufferedInputStream or BufferedOutputStream.
This application (project) on Github:
https://github.com/a-dridi/decorator-tastyburgers