Pizza problem – builder vs decorator
Problem Statement
We need to build the software for a pizza company who wants to prepare different types of pizzas, e.g Chicken Pizza, Flat Bread, Pepperoni Pizza with Extra Cheese, put add on toppings on it.
Lets try to see which design pattern suits this problem statement and under what scenario. Traditionally, for pizza problem, builder pattern is most commonly used. However there are some examples using decorator as well, both the approaches are correct but there is difference in use case. Builder is an object creation pattern whereas decorator is used to change the already built object at runtime.
Lets try to understand this by the examples :
Builder Pattern :
Here the use case is that pizza is prepared in one go with set specifications.
Lets see the Pizza Class :
01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849public
class
Pizza {
private
float
totalPrice =
0
;
private
Size size;
private
Topping topping;
private
Crust crust;
private
Cheese cheese;
public
Size getSize() {
return
size;
}
public
void
setSize(Size size) {
this
.size = size;
}
public
Topping getTopping() {
return
topping;
}
public
void
setTopping(Topping topping) {
this
.topping = topping;
}
public
Crust getCrust() {
return
crust;
}
public
void
setCrust(Crust crust) {
this
.crust = crust;
}
public
Cheese getCheese() {
return
cheese;
}
public
void
setCheese(Cheese cheese) {
this
.cheese = cheese;
}
public
float
getTotalPrice() {
return
totalPrice;
}
public
void
addToPrice(
float
price) {
this
.totalPrice = totalPrice + price;
}
}
The 4 enum classes :
0102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162public
enum
Cheese {
AMERICAN {
public
float
getCost() {
return
40
;
}
}, ITALIAN {
public
float
getCost() {
return
60
;
}
};
public
abstract
float
getCost();
}
public
enum
Crust {
THIN {
public
float
getCost(){
return
70
;
}
} , STUFFED{
public
float
getCost(){
return
90
;
}
};
public
abstract
float
getCost();
}
public
enum
Size {
MEDIUM {
public
float
getCost() {
return
100
;
}
}, LARGE {
public
float
getCost() {
return
160
;
}
};
public
abstract
float
getCost();
}
public
enum
Topping {
PEPPERONI {
public
float
getCost(){
return
30
;
}
}, CHICKEN{
public
float
getCost(){
return
35
;
}
}, MUSHROOM{
public
float
getCost(){
return
20
;
}
};
public
abstract
float
getCost();
}
The PizzaBuilder Class :
01020304050607080910111213141516171819202122232425262728293031public
class
PizzaBuilder {
Pizza pizza =
new
Pizza();
public
PizzaBuilder withTopping(Topping topping) {
pizza.setTopping(topping);
pizza.addToPrice(topping.getCost());
return
this
;
}
public
PizzaBuilder withSize(Size size) {
pizza.setSize(size);
pizza.addToPrice(size.getCost());
return
this
;
}
public
PizzaBuilder withCrust(Crust crust) {
pizza.setCrust(crust);
pizza.addToPrice(crust.getCost());
return
this
;
}
public
Pizza build() {
return
pizza;
}
public
double
calculatePrice() {
return
pizza.getTotalPrice();
}
}
The Test Case :
0102030405060708091011public
class
PizzaBuilderTest {
@Test
public
void
shouldBuildThinCrustChickenPizza(){
Pizza pizza =
new
PizzaBuilder().withCrust(Crust.THIN).withTopping(Topping.CHICKEN).withSize(Size.LARGE).build();
assertEquals(Topping.CHICKEN,pizza.getTopping());
assertEquals(Size.LARGE,pizza.getSize());
assertEquals(Crust.THIN,pizza.getCrust());
assertEquals(
265.0
,pizza.getTotalPrice(),
0
);
}
}
Decorator Pattern :
Decorator Pattern is used to add or remove additional functionalities or responsibilities from the object dynamically without impacting the original object. The use case would be that some base pizza is first prepared and then different specifications are added to it.
Here, we need an interface (Pizza) for the BasicPizza (Concrete Component) that we want to decorate and a class PizzaDecorator that contains reference field of Pizza (decorated) interface.
The Pizza Interface:
1234public
interface
Pizza {
public
String bakePizza();
public
float
getCost();
}
The Base Pizza Implementation :
123456789public
class
BasePizza
implements
Pizza{
public
String bakePizza() {
return
"Basic Pizza";
}
public
float
getCost(){
return
100
;
}
}
PizzaDecorator class :
010203040506070809101112131415public
class
PizzaDecorator
implements
Pizza {
Pizza pizza;
public
PizzaDecorator(Pizza newPizza) {
this
.pizza = newPizza;
}
public
String bakePizza() {
return
pizza.bakePizza();
}
@Override
public
float
getCost() {
return
pizza.getCost();
}
}
The 2 decorators : Mushroom and Pepperoni
01020304050607080910111213141516public
class
Mushroom
extends
PizzaDecorator {
public
Mushroom(Pizza newPizza) {
super
(newPizza);
}
@Override
public
String bakePizza() {
return
super
.bakePizza() + " with Mashroom Topings";
}
@Override
public
float
getCost() {
return
super
.getCost()+
80
;
}
}
01020304050607080910111213141516public
class
Pepperoni
extends
PizzaDecorator {
public
Pepperoni(Pizza newPizza) {
super
(newPizza);
}
@Override
public
String bakePizza() {
return
super
.bakePizza() + " with Pepperoni Toppings";
}
@Override
public
float
getCost() {
return
super
.getCost()+
110
;
}
}
The test Case:
123456789public
class
PizzaDecoratorTest {
@Test
public
void
shouldMakePepperoniPizza(){
Pizza pizza =
new
Pepperoni(
new
BasePizza());
assertEquals("Basic Pizza with Pepperoni Toppings",pizza.bakePizza());
assertEquals(
210.0
,pizza.getCost(),
0
);
}
}
The difference
Patterns like builder and factory (and abstract factory) are used in creation of objects. And the patterns like decorator (also called as structural design patterns) are used for extensibility or to provide structural changes to already created objects.
Both types of patterns largely favour composition over inheritance, so giving this as a differentiator for using builder instead of decorator will not make any sense. Both give behaviour upon runtime rather than inheriting it.
So, one should use builder if he wants to limit the object creation with certain properties/features. For example there are 4-5 attributes which are mandatory to be set before the object is created or we want to freeze object creation until certain attributes are not set yet. Basically, use it instead of constructor – as Joshua Bloch states in Effective Java, 2nd Edition. The builder exposes the attributes the generated object should have, but hides how to set them.
Decorator is used to add new features of an existing object to create a new object. There is no restriction of freezing the object until all its features are added. Both are using composition so they might look similar but they differ largely in their use case and intention.
Another approach could be to use Factory Pattern; if we do not want to expose the attributes and want the creation of a certain pizza “magically” inside then based on some attributes. We will explore this implementation using Factory Pattern in the later post.
Reference: | Pizza problem – builder vs decorator from our JCG partner Anirudh Bhatnagar at the anirudh bhatnagar blog. |
Hi Anirudh,
If you really “believe in writing clean and well tested code”, what do you think about adding the following test to the PizzaBuilderTest?
@wong wong
public void buildLargePizzaFail() {
Pizza pizza = new PizzaBuilder().withSize(Size.LARGE).withSize(Size.LARGE).build();
assertEquals(Size.LARGE, pizza.getSize());
assertEquals(160.0, pizza.getTotalPrice(), 0);
}
What is the result of execution of this test?
Hi Jonatan,
Is your test case valid? You are testing some business rules (two large), which is not a problem of a builder pattern ;).
Regards,
Eric
Hi Jonatan,
Here I am just trying to explain the difference between 2 patterns and not getting into business logic per say.
However, to solve the issue of having some mandatory field like size: we can make it part of the Pizza constructor and remove it from PizzaBuilder avoiding its duplicate creation.
To overcome those cases you can create subclasses PizzaWithSize, PizzaWithCrust etc. Which will force you to use proper order.
Hey Guys,
I’m a beginner and have been use this code to do something similar (Toast and Condiments).
I want to test it but it but I think I am missing something because when I try to run the class is not available.
Do you guys know why this is happening?
regards,
Paulo
Great post. \m/
wanted to check, can we use Strategy pattern here?