Design patterns in life and Ruby - gain an intuitive understanding of OO design patterns by linking them with real-life examples.
Template Method is the most commonly used design pattern in programming and real life.
Before we dive into details of the pattern, let’s learn an important life lesson:
Chipotle 101: How to Order in Chipotle.
There are four steps involved:
- Choose a “vessel”: Burrito vs. Bowl vs. Tacos vs. Salad
- Add meat: Chicken vs Steak vs. Barbacoa vs. Carnitas vs. Vegetarian
- Add toppings: Tomato vs. Corn vs. Green Chili vs. Red Chili
- Add extras & drinks: Chips vs. Guacamole vs. Salsa vs. Beer vs. Soda
For example, my go-to order is Bowl + Steak + (Tomato + Corn) + Guacamole and my friend Amber’s go-to order is Burrito + Chicken + (Green Chili + Red Chili) + (Chips + Soda).
If we code our go-to orders in Ruby, they will look like:
class GoToOrderSihui def vessel Bowl end def meat Steak end def toppings Tomato + Corn end def extras Guacamole end def order meal = vessel meal << meat meal << toppings meal << extras meal end end class GoToOrderAmber def vessel Burrito end def meat Chicken end def toppings Green Chili + Red Chili end def extras Chips + Soda end def order meal = vessel meal << meat meal << toppings meal << extras meal end end
When we order, we put everything we want into the vessel and return the stuffed vessel.
Unfortunately, Amber and I decide to be on a diet for a while. And we decide that when we order from Chipotle, we can only get tomato as a topping and no extras. So our choices are limited to:
- Vessel: Burrito vs. Bowl vs. Tacos vs. Salad
- Meat: Chicken vs. Steak vs. Barbacoa vs. Carnitas vs. Vegetarian
- Toppings: Tomato
- No extras & drinks
During the diet, our go-to orders have to be modified to:
- Sihui: Bowl + Steak + Tomato + No extras & drinks
- Amber: Burrito + Chicken + Tomato + No extras & drinks
Putting our orders down in Ruby, we have the following:
class DietOrderSihui def vessel Bowl end def meat Steak end def toppings Tomato end def extras nil end def order meal = vessel meal << meat meal << toppings meal << extras meal end end class DietOrderAmber def vessel Burrito end def meat Chicken end def toppings Tomato end def extras nil end def order meal = vessel meal << meat meal << toppings meal << extras meal end end
Noticing both our orders have the exact same toppings, extras, and order methods, it makes sense to pull them out as a parent class, DietOrder, and have DietOrderSihui and DietOrderAmber inherit from it.
class DietOrder def vessel raise 'What is your choice?' end def meat raise 'What is your choice?' end def toppings Tomato end def extras nil end def order meal = vessel meal << meat meal << toppings meal << extras meal end end class DietOrderSihui < DietOrder def vessel Bowl end def meat Steak end end class DietOrderAmber < DietOrder def vessel Burrito end def meat Chicken end end
Now our friend Ben wants to join our Chipotle Diet Club, and he likes Tacos with Carnitas. Then his order will be:
class BenDietOrder < DietOrder def vessel Tacos end def meat Carnitas end end
Ta-da, you just learned the Template Method design pattern! =]
Don’t believe me?
Take a look at the definition of the Template Method.
The Template Method pattern is a behavioral design pattern that:
- defines the program skeleton of an algorithm in an operation,
- deferring some steps to subclasses.
It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.
Doesn’t this sound exactly like what we just did with our DietOrder and SihuiDietOrder/AmberDietOrder/BenDietOrder?
DietOrder defines the order skeleton: one can only get tomato as a topping and no extras & drinks, and one orders by picking a vessel and putting everything inside the chosen vessel.
SihuiDietOrder/AmberDietOrder/BenDietOrder redefine the vessel and meat depending on our personal preferences.
Let’s say a month passed by, and Amber and I followed our diet strictly. We decided to reward ourselves with cheat days!
On a cheat day, we have soda as our drinks. And each of us can decide which day of the month to be our cheat days.
Since Ben is new to the club, he decides to stick to the diet strictly for a bit longer.
Let’s see how it looks in Ruby:
class DietOrder def vessel raise 'What is your choice?' end def meat raise 'What is your choice?' end def toppings Tomato end def extras is_cheat_day? ? Soda : nil end def is_cheat_day? false end def order meal = vessel meal << meat meal << toppings meal << extras meal end end
In DietOrder, we ask if today is a cheat day. If so, we can have Soda as an extra. Otherwise, there are no extras. And by default, today is not a cheat day.
Amber and I get to define our own cheat days:
require 'date' class DietOrderSihui < DietOrder def vessel Bowl end def meat Steak end def is_cheat_day? Date.today.day == 10 end end class DietOrderAmber < DietOrder def vessel Burrito end def meat Chicken end def is_cheat_day? Date.today.day == 25 end end
Since Ben is sticking with the diet strictly, he doesn’t get a cheat day.
His class doesn’t need to change.
class BenDietOrder < DietOrder def vessel Tacos end def meat Carnitas end end
The is_cheat_day? method is a hook.
A hook provides a way for a subclass to implement an optional part of an algorithm.
If the subclass doesn’t care about the part, it can skip it and use the default implementation in the parent class.
In our case, is_cheat_day? is optional. SihuiDietOrder and AmberDietOrder implement it because we want to have a cheat day each month. But Ben does not want to have a cheat day. So BenDietOrder skips implementing is_cheat_day? and uses the default one from DietOrder, which always returns false.
Two Object-oriented Design Principles
The Template Method uses two important object-oriented design principles:
1. Encapsulate what varies.
In our case, the varying parts are vessel, meat, and is_cheat_day?. We encapsulate them in subclasses. For the parts that don’t vary, toppings and extras, we leave them in the parent class.
2. The Hollywood Principle: Don’t call us, we’ll call you.
Yes, The Hollywood Principle is a real thing.
In Hollywood, movie producers will tell actors: “Don’t call us, we’ll call you if we find a role that fits you.”
In programming, low-level components can participate in the computation, like AmberDietOrder defining its own is_cheat_day?, but the high-level components control when and how, like DietOrder calls is_cheat_day? within extras.
Takeaways:
One definition =>
The Template Method pattern is a behavioral design pattern that
- defines the program skeleton of an algorithm in an operation,
- deferring some steps to subclasses.
It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.
Two Design Principles =>
1. Encapsulate what varies.
2. The Hollywood Principle: Don’t call us, we’ll call you.
Or…
you can just take away a Chipotle order ?
Next time, we take our design & food adventure to The Cheesecake Factory!
Subscribe so you won't miss it!
Enjoyed the article?
My best content on Software Design, Rails, and Career in Dev. Delivered weekly.
Thanks for the great post. Been struggling to grasp all design patterns. I think I got the right place now. You know how to explain complex ideas in easy ways. Very clear and easy to understand and real life example. Feel happy to see this kind of blog exists !