Home > Back-end >  Why send messages from the superclass to get specialisations in the template method pattern?
Why send messages from the superclass to get specialisations in the template method pattern?

Time:07-19

I've been reading Sandi Metz's book on OOP in Ruby. In the chapter on inheritance there is a section on the template method pattern.

It talks about having a base class with common behaviour, then subclasses that inherit this behaviour and implement their own specialised interfaces as needed.

The book goes on to discuss how to send messages from the base class to the subclass methods to get the specific behaviour and override defaults, like so:

class Bicycle
  attr_reader :size, :chain, :tire_size

  def initialize(**opts)
    @size = opts[:size]
    @chain = opts[:chain] || default_chain
    @tire_size = opts[:tire_size] || default_tire_size
  end

  def default_chain
    "11-speed"
  end

  def default_tire_size
    raise NotImplementedError
  end
end

class RoadBike < Bicycle
  def default_tire_size
    "23"
  end
end

What is the benefit of getting the base class to make this call to a method in a sub class, if the method can be defined in the subclass itself?

CodePudding user response:

To see why is this desirable we need to look at the previous iteration of the code (on p126 in my copy):

class Bicycle
  attr_reader :size, :chain, :tire_size

  def initialize(args={})
    @size      = args[:size]
    @chain     = args[:chain]     || default_chain
    @tire_size = args[:tire_size] || default_tire_size
  end

  def default_chain
    '10-speed'
  end
end

Spot the problem? There is an implicit and undocumented expectation that subclasses will implement default_tire_size. If they don't the code blows up in the constructor.

Sandi says on p128:

Explicitly stating that subclasses are required to implement a message provides useful documentation for those who can be relied upon to read it and useful error messages for those who cannot.

The superclass Bicycle is an abstraction of the behavior common to all concrete subclasses. In this example the default_tire_size in Bicycle's public interface tells interested parties (e.g. maintainers of Bicycle subclasses) that there is an expectation that a default tire size will be set. An exception is raised if the method is not implemented in a subclass. The exception is a clear pointer of what has gone wrong - you ain't implemented something you oughta. Much better than a potentially puzzling undefined local variable or method 'default_tire_size' no?

  • Related