Designing a Domino's Pizza  using Decorator Design Pattern

Designing a Domino's Pizza using Decorator Design Pattern

If you'd ask me to choose one type of food that I could eat until I die, it would for sure be a Pizza, the irony being if I eat Pizza every day I would die sooner than later.

Anyway, it would be interesting to see how can a Pizza making process be designed in terms of a Low-Level Design.
We software engineers like to objectify things, and if you consider Pizza as an object it has to be modular, where you can add toppings on top of it, and change the base if you'd like to.

So, let's start by building a Pizza.. but in code. ( Disclaimer: I hold no responsibility whatsoever if you get hungry by the end of this article )

First things first, let's create a pizza base class :

abstract class BasePizza {
    public Toppings: string[];
    public Name: string;
    public Type: boolean;
    public Cost: number; 

    constructor(name: string, type: boolean, cost: number) {
        this.Name = name;
        this.Type = type;
        this.Toppings = [];
        this.Cost = cost;
    }
}

This looks about right for the minimum features that a pizza can have.

  1. Bunch load of toppings

  2. The shitty name was given by the marketing people

  3. Type denotes if it's veg or non-veg

  4. Cost is the saddest part.

But...each topping can have its own properties, right? So giving it a string type wouldn't do proper justice, so let's create a Topping class

type Topping = {
  Name: string;
  Cost: number;
  Type: boolean;
};

and we can also add common functionalities like Adding discounts, printing toppings, and generating descriptions to our base pizza class.
So, now our base pizza class becomes something like this

abstract class BasePizza {
    public Toppings: Topping[];
    public Name: string;
    public Type: boolean;
    public Cost: number; 

    constructor(name: string, type: boolean, cost: number) {
        this.Name = name;
        this.Type = type;
        this.Toppings = [];
        this.Cost = cost;
    }

    addDiscount(discount: number) {
        if (this.Cost < discount) {
          this.Cost = 0;
        } else {
          this.Cost = this.Cost - discount;
        }
      }

      printToppings() {
        this.Toppings.forEach((topping) =>
          console.log(`${topping.Name} :`, `${topping.Type}`)
        );
      }

      getFoodType = (type: boolean) => (type ? "VEG" : "NON-VEG");

      getDescription() {
        console.log("Pizza Name :", this.Name),
          console.log("Toppings : "),
          this.printToppings();
        console.log("Total Cost: ", this.Cost);
        console.log("Pizza Type : ", this.getFoodType(this.Type));
      }

}

Now comes the actual part, how to add toppings?
Well, the brute force approach would be to just manipulate the Toppings array to add extra toppings.
But, you'd be missing some minor details like if a topping is added, the pizza type can be changed, for example, if you add a non-veg topping on a veg pizza it becomes a non-veg pizza ( a common hack that I use while ordering from dominos )
this will also change the price of the pizza. So, for each topping you add, you'd have to do this calculation again.
Therefore, in order for your code not to be as DRY as my conversation with girls, you could follow drum roll...... THE DECORATOR PATTERN
Well if you pick up any book or go to Wikipedia you'll get a perfectly confusing gibberish definition of what a Decorator pattern is, but since you've come here, you get the perks of simplicity. The decorator pattern just takes in an object, modifies it and returns the modified class object. That's about everything you'd need to know about it, the rest you'll understand once you see the code.

class PizzaToppingDecorator {
  private readonly _basePizza: Pizza;
  private readonly _topping: Topping;

  constructor(basePizza: Pizza, topping: Topping) {
    this._basePizza = basePizza;
    this._topping = topping;
    this._basePizza.Toppings.push(this._topping);
    this._basePizza.Cost += this._topping.Cost;
    this._basePizza.Type = this._basePizza.Type && this._topping.Type;
  }

  add() {
    return this._basePizza;
  }
}

As you can see, I've added the base pizza and the topping in the form of constructor injection, once they're added we do the modifications on the base pizza like changing the cost/ type etc.

Now since my constructor cannot return anything, I've added a helper method just to return the modified class.

Now that we have our Decorator ready, let's create some toppings and make our pizza

const MushroomTopping: Topping = {
  Name: "Mushroom",
  Cost: 70,
  Type: true,
};

const PepperoniTopping: Topping = {
  Name: "Pepperoni",
  Cost: 90,
  Type: false,
};

const ExtraCheeseTopping: Topping = {
  Name: "ExtraCheese",
  Cost: 90,
  Type: true,
};

Let's see the Pizza Decorator in action.

let newPizza: Pizza = new Pizza("Margarita", true, 200);
newPizza = new PizzaToppingDecorator(newPizza, MushroomTopping).add();
newPizza = new PizzaToppingDecorator(newPizza, PepperoniTopping).add();
newPizza.addDiscount(40);
newPizza = new PizzaToppingDecorator(newPizza, ExtraCheeseTopping).add();
newPizza = new PizzaToppingDecorator(newPizza, ExtraCheeseTopping).add();
newPizza.getDescription();

The above code gives the following output

Since you've read all the way to here, I shall share with you, my patent-pending hack for ordering Dominoz pizza, this hack was designed after tons of ordering expertise and is well formulated.
So, if you want to order let's say, Chicken Pepperoni with Cheese Burst, order a Margarita with cheese burst and add pepperoni toppings, you'll save about 100-200 bucks for doing so.

Yep, I know you're hungry, so go ahead and order a Dominoz Pizza. Use the KUN69420 code additional discount

Did you find this article valuable?

Support Kunal Dubey by becoming a sponsor. Any amount is appreciated!