Design Pattern: Prototype and Pizza

Design Pattern: Prototype and Pizza

Design patterns in life and Ruby — gain an intuitive understanding of OO design patterns by linking them with real-life examples.

The prototype pattern is the last creational pattern we will look at in the series.

Here is its definition:

The prototype pattern specifies the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

Before we dive into the details of the pattern, let me tell you a story about a pizza store.

Once upon a time, a wise young lady, Sihui, owned a minimalistic pizza store. The store only offered three types of pizza: pepperoni pizza, chicken pizza, and cheese pizza.

The code for the Pizza class was straightforward.

class Pizza
  attr_reader :name, :toppings, :state
  
  def initialize(name, toppings)
    @name = name
    @toppings = toppings
    @state = 'raw'
  end
  
  def bake
    puts "Baking #{name} ..."
    state = 'baked'
  end
end

A pizza had a name, a list of toppings, a state, and a bake method.

The code for PizzaStore was simple as well.

class PizzaStore
  def take_order(pizza_type)
    case pizza_type
    when 'pepperoni'
      pizza = Pizza.new('Pepperoni Pizza', ['pepperoni', 'shredded mozzarella cheese'])
    when 'chicken'
      pizza = Pizza.new('Chicken Pizza', ['chicken', 'mushroom', 'spinach'])
    else
      pizza = Pizza.new('Cheese Pizza', ['cheese'])
    end
    pizza.bake
    pizza
  end
end

The PizzaStore had a method to take orders, bake and return pizzas.

Using these two classes was easy. One could initialize a store and use it to take orders.

Unfortunately, the minimalistic approach was ahead of its time. Not much people came to the store because of its lack of varieties.

Sihui decided to hire a Chief Pizza Creative Officer, CPCO™, to come up more varieties.

The CPCO™ had a brilliant idea. He suggested the store to use a dynamic pizza menu and offer pizzas based on trends and seasons. For example, if matcha was the new trend, the store would have Matcha Pizza. If it was cherry harvest season, the store would offer Cherry Pizza.

Sihui wanted to try out this dynamic menu approach. But the current implementation of PizzaStore didn’t support a dynamic menu. For example, if someone wanted to add Matcha Pizza to the menu, they would have to open the PizzaStore class and change its code.

class PizzaStore
  def take_order(pizza_type)
    case pizza_type
    when 'matcha'
      pizza = Pizza.new('Matcha Pizza', ['matcha powder'])
    when 'pepperoni'
      pizza = Pizza.new('Pepperoni Pizza', ['pepperoni', 'shredded mozzarella cheese'])
    when 'chicken'
      pizza = Pizza.new('Chicken Pizza', ['chicken', 'mushroom', 'spinach'])
    else
      pizza = Pizza.new('Cheese Pizza', ['cheese'])
    end
    pizza.bake
    pizza
  end
end

To support a dynamic menu, Sihui made some changes to the PizzaStore.

class PizzaStore
  attr_accessor :pizza_prototype_collection
  
  def initialize
    @pizza_prototype_collection = {}
  end

  def take_order(pizza_type)
    pizza_prototype = pizza_prototype_collection[pizza_type]
    raise 'unsupported pizza type' unless pizza_prototype
    
    pizza = pizza_prototype.clone
    pizza.bake
    pizza
  end
end

The new PizzaStore contains a pizza_prototype_collection. The new take_order method, instead of creating new pizzas directly, would grab the corresponding pizza prototype and clone it to create a new pizza. (Clone is a method in Ruby that produces a shallow copy of an object. Many programming languages provide similar methods.)

With these two changes, the new PizzaStore supported a dynamic menu. To add a new type of pizza to the menu, one could create a pizza prototype and add it to the prototype collection at run-time. As long as the corresponding prototype could be found, the PizzaStore would be able to take order for any pizza.

Thanks to the dynamic menu, the pizza store became popular. Since then, there’re always long lines outside of the store with customers waiting to try out new pizzas from the menu. ???

Revisit The Definition of The Prototype Pattern

Woohoo, that’s the pizza story, which is also an example of the prototype pattern.

The prototype pattern specifies the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

In our example, the dynamic menu is a collection of pizza prototypes and the take_order method creates new pizzas by cloning the prototypes.

The Key of The Prototype Pattern

Like other creational patterns, the prototype pattern separates the creation of objects from clients that use the objects.

The key of the pattern is that each prototype is responsible for knowing how to create a corresponding object. 

The agreement between prototypes and clients is that all prototypes provide a clone method and clients use it to create corresponding objects.

Advantages of The Prototype Pattern

Because the client code has no knowledge about object creation, we can:

1. at run-time, specify which object to be created

Without touching the code for PizzaStore or restarting the program, we can easily create a new version of Matcha Pizza and update the pizza_prototype_collection. Then ordering matcha pizza will return the Advanced Matcha Pizza, instead of the old one.

2. instantiate dynamically loaded classes

We can also dynamically load a new class Croissant. Without having to modify the code or restarting the program, the PizzaStore is still able to handle order for the new loaded class.


 

Enjoyed the article?

My best content on Software Design, Rails, and Career in Dev. Delivered weekly.

Unsubscribe at anytime. I'll never spam you. Powered by ConvertKit

1 Comment Design Pattern: Prototype and Pizza

Leave A Comment

Your email address will not be published. Required fields are marked *