Reputation: 11
I am trying to figure out how to use the users input and output certain information depending on the user input. Can someone provide a simple example of using the decorator pattern for a simple pizza shop for example?
So I know how to go about the decorator pattern, it’s just the user input portion that has me stuck. So let’s say the user wants to make a pizza, they will first choose the size of pizza, and then add ASB many toppings as they want. Then when they have finished, they will see their total price for what they added as well as what they added (like a reciept). This is in Java.
Upvotes: 1
Views: 2695
Reputation: 9570
A decorator is a class that extends the functionality of another class. A decorator usually implements the same interface so that a decorated object can be used instead of the basic one. A good example is a compressor and/or encrypter applied over a file or, more generally, over a data stream implementation, as shown in the answer by @nits.kk.
In a case of pizza, we should define what behavior we need:
public interface Pizza {
public String getIngredients(); // comma separated
public double getTotalPrice();
}
A pizza consists of two main types of ingredients: a mandatory single base and optional multiple toppings. Each ingredient has its own price.
public class PizzaIngredient {
private double getPrice() {
return 0.0;
}
}
The pizza base is itself a simplest possible pizza, so it must implement the Pizza
interface. It has a size as its attribute (and the price, of course). We could implement a size as a separate class, but I don't consider it reasonable - it's not general enough to be useful outside the pizza universe and not complex enough to deserve its own interface.
public class PizzaBase extends PizzaIngredient implements Pizza {
public PizzaBase(String size) {
this.size = size;
}
public String getIngredients() {
return size + " base"; // the only ingredient is this base
}
public double getTotalPrice() {
return getPrice(); // the base-only pizza costs the base cost
}
private double getPrice() {
if(size == "small")
return 2.0;
if(size == "medium")
return 2.5;
return 3.0; // large and undefined
}
private final String size;
}
Now we need toppings. They wil be added on top of pizza as decorators: a pizza plus a topping is a pizza, too, so the topmost topping will represent the whole composition. Such pizza's ingredients list is a list of ingredients of the underlying pizza plus the name of its topmost topping. Similarly the total price.
public class PizzaTopping extends PizzaIngredient implements Pizza {
public PizzaTopping(String name, Pizza pizza) {
this.name = name;
this.pizza = pizza;
}
public String getIngredients() {
return pizza.getIngredients() + ", " + getName();
}
public double getTotalPrice() {
return pizza.getTotalPrice() + getPrice();
}
public String getName() {
return name;
}
private final String name;
private final Pizza pizza;
}
Let's define some concrete toppings:
public class MozzarellaTopping extends PizzaTopping {
public MozzarellaTopping(Pizza pizza) {
super("mozzarella", pizza);
}
private double getPrice() {
return 0.5;
}
}
public class MushroomTopping extends PizzaTopping {
public MushroomTopping(Pizza pizza) {
super("mushroom", pizza);
}
private double getPrice() {
return 2.0;
}
}
public class PepperoniTopping extends PizzaTopping {
public PepperoniTopping(Pizza pizza) {
super("pepperoni", pizza);
}
private double getPrice() {
return 1.5;
}
}
public class GreenOliveTopping extends PizzaTopping {
public GreenOliveTopping(Pizza pizza) {
super("green olive", pizza);
}
private double getPrice() {
return 1.2;
}
}
Okay, that's lots of classes; but which of them and when shall we need?
Here a Factory joins the team. A factory is a class to create objects of some classes. It's used to hide the creation details behind the scenes, especially when the objects created are complicated or are of different concrete classes. When resulting objects are created as standalone entities, the factory class can be just a namespace with a static method in it. OTOH, if objects are created in some context, the factory could be an object associated with the context (for example, parametrized) and using that context in creation process.
We may use a factory to create pizza ingredients at will, according to user input. Most ingredients are toppings, which must be applied on top of already existing pizza, so let's pass the pizza to a factory to receive the decorated pizza as a result. The special case is creating a pizza base, which is not applied on another pizza; in this case the pizza
parameter is ignored, so we may pass null
.
public class PizzaFactory {
public static Pizza getPizza(Pizza pizza, String name)
{
if ( name.equals("small") || name.equals("medium") || name.equals("large") )
return new PizzaBase(name);
else if ( name.equals("mozzarella") )
return new MozzarellaTopping(pizza); // add topping to the pizza
else if ( name.equals("mushroom") )
return new MushroomTopping(pizza);
else if ( name.equals("pepperoni") )
return new PepperoniTopping(pizza);
else if ( name.equals("green olive") )
return new GreenOliveTopping(pizza);
return null;
}
}
Now we're ready to build our pizza.
class PizzaTest {
public static void main(String[] args) {
DecimalFormat priceFormat = new DecimalFormat("#.##");
Pizza pizza;
pizza = PizzaFactory.getPizza(null, "small");
System.out.println("The small pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
pizza = PizzaFactory.getPizza(null, "medium");
pizza = PizzaFactory.getPizza(pizza, "mozzarella");
pizza = PizzaFactory.getPizza(pizza, "green olive");
System.out.println("The medium pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
String largePizzaOrder[] = { "large", "mozzarella", "pepperoni",
"mushroom", "mozzarella", "green olive" };
pizza = null;
for (String cmd : largePizzaOrder)
pizza = PizzaFactory.getPizza(pizza, cmd);
System.out.println("The large pizza is: " + pizza.getIngredients());
System.out.println("It costs " + priceFormat.format(pizza.getTotalCost()));
}
}
Warning: there are some pitfalls and shortcuts in the above code.
The most important is the lack of validation of input: when an unexpected command arrives, the factory will return null
which will cause a crash on future use of getIngredients()
or getTotalCost()
.
Another one is hard-coding prices into concrete classes. An actual solution would have to use some price list and fetch prices either on an ingredient creation (and store fetched prices in the ingredient objects) or on use, i.e. in the getCost()
method (which would require some access to the price list from pizzas' ingredients).
Upvotes: 1
Reputation: 5316
Lets see below points to start with
enhance existing behavior of an object at run time without destroying the existing interface of the object
. Question : An object is derieved from its class which is compile time. Now how would you go on enhancing the behaviour ?
Answer : By using Decorator pattern also known as wrapper.
Example : You have a file which can be encrypted, lets say methods of encryption are currently 5, the result will be encrypted file. Encrypted file can again be encrypted. Additionally lets assume there are 5 ways to zip the file which can later increase also. A file can be encrypted by methodEA then can be zipped by MethodZA then again can be encrypted by methodEB, similar sequences can make different result files.
One of the good way is as below.
public class TextFile{
public void create(){/*somecode*/};
public void format(){//code for default plain text};
}
public class AEncryptedFile extends TextFile{
private TextFile wrapped;
public AEncryptedFile(TextFile file){
this.wrapped = file;
}
public void format(){
super.format();
//add enhacements for encryption type A
}
}
public class BEncryptedFile extends TextFile{
private TextFile wrapped;
public BEncryptedFile(TextFile file){
this.wrapped = file;
}
public void format(){
super.format();
//add enhacements for encryption type B
}
}
public class AZippedFile extends TextFile{
private TextFile wrapped;
public BEncryptedFile(TextFile file){
this.wrapped = file;
}
public void format(){
super.format();
//add enhacements for zip type A
}
}
public class BZippedFile extends TextFile{
private TextFile wrapped;
public BEncryptedFile(TextFile file){
this.wrapped = file;
}
public void format(){
super.format();
//add enhacements for zip type B
}
}
public void UserClass{
public static void main(String[] args){
TextFile file = new BZippedFile(new AEncryptedFile(new TextFile()));
file.format();
}
In above example code it can be said
An object of TextFile has been decorated (by wrapping) by AEncryptedFile object whichis further decorated by BZippedFile, in each of these decoration additional enhancement has been made to existing object
This way existing object of TextFile can be passed to various methods at runtime and the object can be decorated by wrapping it in another object of subtype of TextFile.
Note : Decorator pattern implementation has a structure of a LinkedList.
Upvotes: 1
Reputation: 10003
Some related questions: When would you use the Builder Pattern? and Java Decorative Pattern Pizza Topping. also see my answer to Adding State in Decorator Pattern for something different.
Upvotes: 0
Reputation: 521429
Your expectation/understanding of the purpose of the decorator pattern might be slightly off. The decorator pattern is intended to wrap an existing set of functionality in order to offer some new functionality, in addition to the functionality which is already there.
A better pizza example would be taking a pizza class which can do the following things:
and then trying to add functionality which can serve salads. So a simplified version of this pizza class might look like this:
public class Pizzeria {
public String orderPizza() {
System.out.println("you ordered a pizza");
}
public String orderSoftDrink() {
System.out.println("you ordered a soft drink");
}
}
To implement the decorator pattern here, we wrap the existing Pizzeria
class, and then add some new function public String orderPizza() {
System.out.println("you ordered a pizza");
}
public String orderSoftDrink() {
System.out.println("you ordered a soft drink");
}
ality:
public class NewPizzeria {
private Pizzeria pizzeria;
public NewPizzeria() {
pizzeria = new Pizzeria();
}
public String orderPizza() {
pizzeria.orderPizza();
}
public String orderSoftDrink() {
pizzeria.orderSoftDrink();
}
public String orderSalad() {
System.out.println("you ordered a salad");
}
}
The key point here is that the NewPizzeria
class "owns" its own Pizzeria
object. For the most part, it just rexposes the same functionality which Pizzeria
already has. But, it also adds some new functionality of its own.
The decorator design pattern is useful in cases where a class already exists which mostly fits your needs, but you need something else and you also cannot rewrite that class (e.g. because it is part of some library). In this case, wrapping that class and using the decorator pattern is one good option.
Upvotes: 0
Reputation: 13934
Concept-wise, in decorator pattern, the output of one processing goes as input to another processing.
So in your case, it should be like this:
getToppingFoo(getToppingBar(...(getXBaseSizePizzaCost())
which resolves to :
FooToppingCost + (BarToppingCost + ... ( Cost of pizza with base of X size )
Further, you can define a factory class to get Pizza of object of various sizes, say Standard, Medium, Large. Logic is same irrespective of language you pick.
Upvotes: 0