Decorator Pattern Explained

In recent posts, we identified what issues can arise if we keep going with the design shown in Class Explosion and Etymology Of Decorator Pattern. Lets take a quick look at what fundamental design problems exists in this design.

Exsisting Class Hierarchy

Exsisting Class Hierarchy

Here, we are hard-coding toppings of Pizza at compiile-time. Which are actually concrete entities in the real world. So in a way, they are Add-on objects that can be used to decorate our pizza. If we want to add new item as toppings, we need to modify the class – violation of Open-closed principle (OCP). Ideally, we should be able to add any new topping “without modifying the class”. As OCP says,

“Classes should be closed for modification and open for enhancement.”

So we need capability to add behavior to the object at run-time and provide the client of Pizza class greater flexibility. Now how to achieve this. Let’s note down what approach we need to follow to get maximum flexibility in design (maximum in terms of toppings and calculating its cost) in creating one FreshVeggiePizza with Paneer and Olives.

1. Take plain Pizza object.
2. Decorate it with Paneer.
3. Decorate it with Olives.
4. Call cost() method, which will be delegated to “Add-on” or “Decorator” objects also to add up their cost.

Here is the diagram that will clarify exactly how the cost will be calculated for this pizza.

Cost Of FreshVeggiePizza With Paneer & Olives

Cost Of FreshVeggiePizza With Paneer & Olives

There is one important point in this diagram if you noticed. There has to be cost() method in each “Decorator” object as well as “FreshVeggiePizza”. So we will create one super class “Decorator” for each topping item. Food class will have cost() and getDescription() methods. In addition to that, we should be able to print description of the food according to what toppings are added, rather than tucking description into Pizza class.

Class Design With Decorator Pattern

Class Design With Decorator Pattern

Here is the code to realize this diagram.

public abstract class Food {
private String description;
public String getDescription(){
return description;
}

public abstract void cost();
}

public abstract class Decorator extends Food {

public abstract String getDescription();
public abstract void cost();
}

public class FreshVeggiePizza extends Food {

public FreshVeggiePizza(){
description = “Fresh Veggie Pizza”;
}
public double cost(){
return 100;
}
}

public class Paneer extends Decorator {
private Food food;

public Paneer(Food food){
this.food = food;
}

public String getDescription(){
return food.getDescription + “, with Paneer”;
}

public double cost(){
return food.cost() + 15;
}
}

Let’s take a look at what happens in this code. Here our “Non-decorator” food will return description set in the constructor. So we have default implementation of getDescription() in Food class. We want to enforce each Decorator to implement getDescription() method in such a way so that it also reflects object it is decorating. So we have getDescription() method abstract in Decorator class. Now, Decorator classes like Paneer also needs to know which food it is decorating, so we need to pass the decorated food in the constructor of Paneer. In this way, we can create our other pizza and sandwich classes and their decorators.

Recipe Of FreshVeggiePizza with Paneer and Olive :

1.  Create classic FreshVeggiePizza.

Food pizza = new FreshVeggiePizza();

2. Put a layer of Paneer over it.

pizza= new Paneer(pizza );

3. Spread some olives over pizza.

pizza = new Olive(pizza);

Your FreshVeggiePizza with paneer and olives is ready. Now if you print price of the pizza, it will calculate cost for FreshVeggiePizza + cost of Paneer + cost of Olive. This is the fair level of dynamism that we can expect at this stage of Objectville.

Open-closed Principal Achieved:

We don’t need to change our Pizza class at all to add toppings on it that may change cost of the pizza. We don’t care if cost of paneer or olive booms, because we can parameterize those attributes and it is “decorated” over pizzas rather than tightly coupling it into pizza.

Decorators In Practice :

To take a look at real life decorators, have an eye over java.io classes. Replace Food with InputStream, Decorator with FilterInputStream, FreshVeggiePizza with FileInputStream/ByteArrayInputStream/StringBufferInputStream and Paneer with PushbackInputStream/BufferedInputStream/DataInputStream.

Summary:

Identify properties and methods common to decorators and client components (in our case, client is Pizza). Create super class for these common methods. Create Decorator super-class and identify methods which need to be implemented by each decorator. Pass reference of client in each decorator to allow decorator to add its own properties. Hence, job of decorator is to extend behavior of client and that too without modifying existing code.

2 Comments
Write a comment