Template Pattern & Inheritance (another argument against ‘super’)

We’re reading my favorite programming book ever: POODR. I haven’t read it since I was about to start Dev Bootcamp, so it’s interesting to come back a year and a half later with (very slightly) greater than zero experience. Some of the reading touched on our python book’s recommendations about not using ‘super’ that I wrote about earlier this week.

Metz doesn’t encourage readers to shy away from ‘super’ because of its complexity (since Ruby doesn’t support multiple inheritance, its ‘super’ is more straightforward). Instead, she cautions against using it because it hardcodes information about superclass algorithms into subclasses.

Again, coming up with examples myself is helpful to me, but hers are much better. You can find them in chapter 6 and 7 of POODR.

I’m going to model crew members on a space ship. Crew members all have names and medical information, and need to know what to do to prepare the ship for takeoff.

class CrewMember
attr_reader :seat_buckled, :name, :blood_type
  def initialize(args)
@name = args[:name] || "Jane Doe"
@blood_type = args[:blood_type] # space is dangerous!
@seat_buckled = false
end
  def liftoff_checklist     
list = []
list << "Buckle seat belt!" if !seat_buckled
list
end
end

“Putting control of the timing in the superclass means the algorithm can change without forcing changes upon the subclasses” -p.135

class Engineer < CrewMember
def liftoff_checklist
super << "Check space engines"
end
end
class Pilot < CrewMember
def liftoff_checklist
super << "Engage warp drive"
end
end
class ShipCounselor < CrewMember
def liftoff_checklist
super << "Issue vague, pointless warning"
end
end
>> troi = ShipCounselor.new({name: "Troi", blood_type: "O"})
>> troi.liftoff_checklist
=>["Buckle seat belt!", "Issue vague, pointless warning"]
# this is a more advanced Enterprise where they finally have seat belts so the whole crew doesn't go falling all over the bridge every time they get fired on

This works, but according to Metz, its bad news because each of the subclasses has information about the superclass’s liftoff_checklist: they know the name of the method, that it returns a list, and that it strings can be shoveled into that list. It knows about the algorithm that builds liftoff_checklists, which means parts of this information is duplicated across the superclass and all of its subclasses. This also tightly couples these objects, and sets a trap for future forgetful developers who might not add in the call to ‘super’. Rather than calling superclass methods from each of the subclasses, Metz recommends using the template method, which she defines as “defining a basic structure in the superclass and sending messages to acquire subclass-specific behavior”

class CrewMember
attr_reader :seat_buckled, :name, :blood_type

def initialize(args={})
@name = args[:name] || "Jane Doe"
@blood_type = args[:blood_type] # still dangerous
@seat_buckled = false
end
  def liftoff_checklist      # returns list of completed tasks
local_checklist << "Buckle Seat Belt" if !seat_buckled
local_checklist
end
  def local_checklist
[]
end
end

Now, instead of expecting downstream classes to call ‘super’, CrewMember provides a local_checklist that subclasses have the option to overwrite.

class Engineer < CrewMember
def local_checklist
["Check space engines"]
end
end
class Pilot < CrewMember
def local_checklist
["Engage warp drive"]
end
end
class ShipCounselor < CrewMember
def local_checklist
["Issue vague, pointless warning"]
end
end
>> Pilot.new().liftoff_checklist
=> [“Engage warp drive”, “Buckle Seat Belt”]

Interestingly, the template method pattern doesn’t work for a third level of hierarchy, (your overwrites would overwrite your overwrites :P ) but I guess that’s ok since we should all be creating shallow hierarchies anyway.

Like what you read? Give Nicole McCabe a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.