Before talking about Decorator design pattern, think of decorators in real life. Let's consider a very common example.
In an ice cream parlor you can order ice creams in Vanilla, Butter Scotch, Chocolate and Pineapple flavors with or without an assortment of toppings. Here are your choices:
Each of these toppings is a 'Decorator'. As evident from name itself, Decorators in real life 'decorate' an existing object. They ADD something to an existing object. Decorator design pattern also solves the same problem.
If you think of implementing this system, your first instinct would be to create a base class Icecream and make Vanilla, ButterScotch, Chocolate and Pineapple extend it. Correct.
public interface Icecream {
String getDescription();
double getCost();
}
public class ButterScotch implements Icecream{
private final double cost = 125.90;
public String getDescription(){
return("This is ButterScotch icecream");
}
public double getCost(){
return cost;
}
}
public class Vanilla implements Icecream{
private final double cost = 75.90;
public String getDescription(){
return("This is Vanilla icecream");
}
public double getCost(){
return cost;
}
}
Now since a client may want any ice cream with or without one or more toppings, the behavior has to be decided on run time. You will notice that one or more decorators (toppings) come into picture now.
Decorator pattern is used when we have to attach additional responsibilities to an object at run time. Note the emphasis on 'object'. As I said earlier also, since there can be various combinations of toppings, behavior can be different for different objects. If you consider each ice cream as a different object, one of your ice creams could be vanilla with chocolate sauce while other could be pineapple with strawberry sauce. In both cases end product is an Icecream and it has a description and a cost, but obviously it will differ in both cases. Inheritance will apply to entire class hence it has to be ruled out.
To put simply, Decorator pattern makes use of wrapper classes which wrap the core object and 'decorate' core object by repeated invocations.
Before we create individual wrapper classes for each topping, we create an abstract decorator class which implements the base interface.
abstract class IcecreamDecorator implements Icecream{
abstract public String getDescription();
abstract public double getCost();
}
and then make concrete decorator class for each topping:
public class MapleSyrup extends IcecreamDecorator {
Icecream icecream;
public MapleSyrup(Icecream icecream){
this.icecream = icecream;
System.out.println("Created Maple Syrup");
}
public String getDescription(){
return(icecream.getDescription()+ " with Maple Syrup Topping");
}
public double getCost(){
return icecream.getCost() + 25;
}
}
Looking at code of MapleSyrup you should understand why did our IcecreamDecorator have to implement the base interface. Because both getDescription and getCost work on top of the object it is wrapping, concrete decorator class has to expose same properties as base class. If you can get description and cost of a plain vanilla ice cream, you should also be able to get description and cost of a vanilla ice cream topped with maple syrup.
Do you really need an interface as base class (Icecream)? No, it can as well be an abstract class if there are some methods that you will give a default implementation for and then override in your sub classes.
If your design is too simple you may feel that you can do away with decorator (in our case IcecreamDecorator) abstract class completely and have an instance of the base interface/abstract class (in our case Icecream) directly as an instance variable of your concrete decorator classes, something like this:
public class FruitAndNuts implements Icecream {
Icecream icecream;
public FruitAndNuts(Icecream icecream){
this.icecream = icecream;
}
public String getDescription(){
return icecream.getDescription()+" with Fruits and nuts";
}
public double getCost(){
return icecream.getCost()+45;
}
}
But this is bad design if you consider the fact that Icecream may undergo modification later. Maybe we add a new method 'getStorageTemperature()' to it and then you MUST make a change to all these concrete decorator classes which is not really a good idea. Whereas having an abstract class in form of IcecreamDecorator, you can give a default implementation of getStorageTemperature()and then override it only when needed in your concrete decorator classes.
Before I wrap this post, here is the code to take all the above code for a spin:
public class IcecreamParlorClient {
public static void main(String[] args){
Icecream order1 = new Vanilla();
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
order1 = new MapleSyrup(order1);
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
order1 = new FruitAndNuts(order1);
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
}
}
Output:
Orde1 description This is Vanilla icecream Order1 cost: 75.9
Created Maple Syrup
Orde1 description This is Vanilla icecream with Maple Syrup Topping Order1 cost: 100.9
Created Fruits and Nuts
Orde1 description This is Vanilla icecream with Maple Syrup Topping with Fruits and nuts Order1 cost: 145.9
In an ice cream parlor you can order ice creams in Vanilla, Butter Scotch, Chocolate and Pineapple flavors with or without an assortment of toppings. Here are your choices:
- Chocolate sauce
- Strawberry sauce
- Maple Syrup
- Fruits and nuts
- or just a Cherry
You can even do a mix and match of toppings according to your liking. So you can have a scoop (or two) of vanilla ice cream with Strawberry syrup topped with fruits and nuts and a cherry.
Each of these toppings is a 'Decorator'. As evident from name itself, Decorators in real life 'decorate' an existing object. They ADD something to an existing object. Decorator design pattern also solves the same problem.
If you think of implementing this system, your first instinct would be to create a base class Icecream and make Vanilla, ButterScotch, Chocolate and Pineapple extend it. Correct.
public interface Icecream {
String getDescription();
double getCost();
}
public class ButterScotch implements Icecream{
private final double cost = 125.90;
public String getDescription(){
return("This is ButterScotch icecream");
}
public double getCost(){
return cost;
}
}
public class Vanilla implements Icecream{
private final double cost = 75.90;
public String getDescription(){
return("This is Vanilla icecream");
}
public double getCost(){
return cost;
}
}
Now since a client may want any ice cream with or without one or more toppings, the behavior has to be decided on run time. You will notice that one or more decorators (toppings) come into picture now.
Decorator pattern is used when we have to attach additional responsibilities to an object at run time. Note the emphasis on 'object'. As I said earlier also, since there can be various combinations of toppings, behavior can be different for different objects. If you consider each ice cream as a different object, one of your ice creams could be vanilla with chocolate sauce while other could be pineapple with strawberry sauce. In both cases end product is an Icecream and it has a description and a cost, but obviously it will differ in both cases. Inheritance will apply to entire class hence it has to be ruled out.
To put simply, Decorator pattern makes use of wrapper classes which wrap the core object and 'decorate' core object by repeated invocations.
Before we create individual wrapper classes for each topping, we create an abstract decorator class which implements the base interface.
abstract class IcecreamDecorator implements Icecream{
abstract public String getDescription();
abstract public double getCost();
}
and then make concrete decorator class for each topping:
public class MapleSyrup extends IcecreamDecorator {
Icecream icecream;
public MapleSyrup(Icecream icecream){
this.icecream = icecream;
System.out.println("Created Maple Syrup");
}
public String getDescription(){
return(icecream.getDescription()+ " with Maple Syrup Topping");
}
public double getCost(){
return icecream.getCost() + 25;
}
}
Looking at code of MapleSyrup you should understand why did our IcecreamDecorator have to implement the base interface. Because both getDescription and getCost work on top of the object it is wrapping, concrete decorator class has to expose same properties as base class. If you can get description and cost of a plain vanilla ice cream, you should also be able to get description and cost of a vanilla ice cream topped with maple syrup.
Do you really need an interface as base class (Icecream)? No, it can as well be an abstract class if there are some methods that you will give a default implementation for and then override in your sub classes.
If your design is too simple you may feel that you can do away with decorator (in our case IcecreamDecorator) abstract class completely and have an instance of the base interface/abstract class (in our case Icecream) directly as an instance variable of your concrete decorator classes, something like this:
public class FruitAndNuts implements Icecream {
Icecream icecream;
public FruitAndNuts(Icecream icecream){
this.icecream = icecream;
}
public String getDescription(){
return icecream.getDescription()+" with Fruits and nuts";
}
public double getCost(){
return icecream.getCost()+45;
}
}
But this is bad design if you consider the fact that Icecream may undergo modification later. Maybe we add a new method 'getStorageTemperature()' to it and then you MUST make a change to all these concrete decorator classes which is not really a good idea. Whereas having an abstract class in form of IcecreamDecorator, you can give a default implementation of getStorageTemperature()and then override it only when needed in your concrete decorator classes.
Before I wrap this post, here is the code to take all the above code for a spin:
public class IcecreamParlorClient {
public static void main(String[] args){
Icecream order1 = new Vanilla();
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
order1 = new MapleSyrup(order1);
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
order1 = new FruitAndNuts(order1);
System.out.println("Order1 description "+ order1.getDescription()+ " Order1 cost: "+order1.getCost());
}
}
Output:
Orde1 description This is Vanilla icecream Order1 cost: 75.9
Created Maple Syrup
Orde1 description This is Vanilla icecream with Maple Syrup Topping Order1 cost: 100.9
Created Fruits and Nuts
Orde1 description This is Vanilla icecream with Maple Syrup Topping with Fruits and nuts Order1 cost: 145.9
See how each time a new topping is being added, description and cost is being calculated on top of its wrapped object.
I hope this post cleared any doubts you may have had with this pattern.
Comments