A couple friends and I have a web development book club. The goal is to learn and grow together as software engineers. But really, sometimes it’s more like a support group, where we use a lot of 😞 and 😢 to discuss our respective screw-ups at work and the impossible vastness of information about web development and how we only know the smallest tiniest minusculest amount of it. We’re a fun group…
The first book we read together was Practical Object-Oriented Design in Ruby by Sandi Metz. Really amazing resource for new Ruby devs (or probably any new dev using an OO language). For our next book, we just started reading Design Patterns in Ruby by Russ Olsen, so this will be the first in a planned series about the various design patterns discussed in the book.
The first pattern is the template method pattern.
Inheritance + Polymorphism
The template method pattern is an object-oriented design pattern that relies on inheritance and polymorphism. By inheritance, I am talking about the parent-child relationship between classes, where a class can be defined as a subclass of another and inherits all of that superclass’ methods. By polymorphism, I am talking about the ability to invoke the same method on multiple objects of different classes.
# Inheritance class Computer def initialize @on = false end def power_on @on = true puts 'Booting up...' end end class Laptop < Computer def close_lid ... end def open_lid ... end end my_laptop = Laptop.new my_laptop.power_on # => Booting up...
In the basic inheritance example above, the
Computer class defines the
power_on method. The
Laptop class is defined as a subclass of
also defines its own set of methods. But since
Computer is its superclass,
Laptop inherit the
power_on method and are able to be sent that
# Polymorphism def too_big?(obj) obj.length > 10 end str = 'Hello, World!' arr = [1, 2, 3] too_big?(str) # => true too_big?(arr) # => false
The polymorphism example is showing that both the
length method. There is some role that they are both playing,
perhaps the role of a
too_big? method doesn’t care whether it
is being passed a string or an array; it just cares whether it is being passed
an object that is countable and has implemented
length. Being able to send the
same message to objects that are instances of different classes is polymorphism.
The Template Method Pattern
The template method pattern combines the concepts of inheritance and polymorphism to solve the common programming problem of needing to allow for some level of variation in an algorithm.
If you were a strange person, you might think of your life in terms of Ruby
objects and messages that you send to these objects. So if you wanted to throw a
birthday party for your niece, you might have a
Party class with a
throw_party method that does all the steps necessary for giving your niece a
successful birthday party.
class Party def throw_party invite_guests clean_house buy_birthday_cake welcome_guests sing_happy_birthday kick_out_guests clean_house end private ...all the private methods being used in throw_party... end
What if you wanted to throw another party, maybe a game night for you and some
friends? It would be great to reuse this
Party class and the
method, but while some parts of the method are general to any party, some parts
are very specific to birthday parties.
We could put in some conditionals:
class Party def throw_party(type) invite_guests clean_house if type == 'birthday' buy_birthday_cake elsif type == 'game_night' buy_board_games end welcome_guests if type == 'birthday' sing_happy_birthday elsif type == 'game_night' play_games end kick_out_guests clean_house end private ...all the private methods being used in throw_party... end
Yikes! That is not the best solution. Any time you want to throw a different
kind of party, you will have to modify this method until it grows out of
control. And you’ll have to add more and more private methods to this class. But
having completely separate classes for each party type isn’t ideal, either,
since we saw that most of the
throw_party logic is common to any type of
The template method pattern is a way of separating the parts that are the same from the parts that are different. The parts that are the same get implemented in a parent class while the parts that are different are implemented in the subclasses.
The only parts that differ between the two party types are the buying of
supplies and the party activities. So we can create an abstract Party class
(abstract because there will be no instances of it) that will serve as the
superclass for the
class Party def throw_party invite_guests buy_supplies clean_house welcome_guests do_activities kick_out_guests clean_house end private def buy_supplies raise 'Not implemented: buy_supplies' end def do_activities raise 'Not implemented: do_activities' end ...all the other private methods being used in throw_party... end
Now, all the parts that are the same can stay in
Party, and the parts that are
different can be defined in our
class BirthdayParty < Party private def buy_supplies buy_birthday_cake end def do_activities sing_happy_birthday end def buy_birthday_cake ... end def sing_happy_birthday ... end end class GameNight < Party private def buy_supplies buy_board_games end def do_activities play_games end def buy_board_games ... end def play_games ... end end
The superclass is the template for what happens for a party, and each subclass implements just the parts that vary from the template.
In the current implementation of
Party, an error is raised for methods that
are meant to be implemented in the subclasses. This provides a clear signal to
anyone wanting to implement a new type of party that
do_activities are methods that should be implemented in the new party type.
Another approach would be to implement these methods in
Party with some
default behavior that the subclasses then override.
Back to the use of inheritance and polymorphism. It is fairly clear from the
GameNight are both subclasses of
every method defined in
Party is also available to the subclasses. In our
example, the only publicly available method in either subclass is the
throw_party method defined in
Party. That’s where the polymorphism comes in.
Whatever object decides to throw a party can do so by sending the
message to an instance of either
GameNight. It doesn’t care
which type of object it sends the message to, and even though the results may be
different, both types of objects will respond to the message.