DECORATOR
This pattern belongs to the group of structural patterns.
Decorators provide flexibility similar to that of inheritance, but in return offer significantly expanded functionality.
This pattern expands the structure without interfering with the existing objects. It is helpful when we need to dynamically extend an object. What does it mean dynamically? This means that we do not want every object of a given class to have this functionality, but only in specific conditions the object should be extended with it.
This is one of the few patterns that recommends inheriting an abstract class from an abstract class. It is very similar to the Adapter. Wraps the component, which is the class we want to extend.
The decorator can also be used when we do not want or we cannot change the structure of objects and we need to make some changes in the function’s operation, or to extend the object.
Based on the hotdog booth, let’s try to understand the essence of this pattern.
To begin with, let’s create two abstract classes: the Hotdog class and the Toppings class:
public abstract class Hotdog {
String name = "Too early to say..";
public String getName(){ return name; }
public abstract double price();
}
public abstract class Toppings extends Hotdog{
public abstract String getName();
}
Now, based on the Hotdog class, let’s create two exemplary types of hotdogs:
public class Plain extends Hotdog{
public Plain(){ name = "Bun with a sausage"; }
@Override
public double price() { return 2.50; }
}
public class Double extends Hotdog{
public Double(){ name = "Bun with 2 sausages"; }
@Override
public double price() { return 3.75; }
}
Next, based on the Toppings class, let’s add a few options:
public class Onion extends Toppings{
Hotdog hotdog;
public Onion(Hotdog hotdog){
this.hotdog = hotdog;
}
public String getName(){
return hotdog.getName() + ", Crispy onion";
}
@Override
public double price() {
return hotdog.price() + 0.30;
}
}
public class Ketchup extends Toppings{
Hotdog hotdog;
public Ketchup(Hotdog hotdog){
this.hotdog = hotdog;
}
public String getName(){
return hotdog.getName() + ", Ketchup";
}
@Override
public double price() {
return hotdog.price() + 0.10;
}
}
public class Mayonnaise extends Toppings{
Hotdog hotdog;
public Mayonnaise(Hotdog hotdog){
this.hotdog = hotdog;
}
public String getName(){
return hotdog.getName() + ", Mayonnaise";
}
@Override
public double price() {
return hotdog.price() + 0.10;
}
}
All that’s left for us to do is create a hotdog booth and ask the lady from the booth for something to eat:
public class HotdogBooth {
public static void main(String[] args){
Hotdog hotdog1 = new Plain();
System.out.println(hotdog1.getName() + " " + hotdog1.price() + "€");
Hotdog hotdog2 = new Double();
hotdog2 = new Ketchup(hotdog2);
hotdog2 = new Mayonnaise(hotdog2);
hotdog2 = new Onion(hotdog2);
System.out.println(hotdog2.getName() + " " + hotdog2.price() + "€");
}
}
Let’s see below what we got:
Bun with a sausage 2.5€
Bun with 2 sausages, Ketchup, Mayonnaise, Crispy onion 4.25€
It should be remembered that the use of decorators can result in the appearance of a large number of small objects in the project and the overuse of decorators can lead to a significant increase in the complexity of the code.