Inheritance is, at it's core, a mechanism for automatic message delegation. It defines a forward path for not-understood messages.
Subclasses are specializations of their superclasses
Any class that implements the template method pattern must supply an implementation for every message it sends, even if all it does is raise a NotImplementedError.
One way around the super problem is for the superclass to send hook messages at appropriate integration points. If a subclass needs to add or modify the behavior of the superclass, it can implement an appropriate hook method. As noted above, the superclass must always have an implementation any shared methods; though usually the superclass’s hook method is just a no-op.
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
post_initialize(args)
end
def spares
{ tire_size: tire_size,
chain: chain}.merge(local_spares)
end
# It is important that the parent class also have a default_tire_size method - even if all it does is raise a NotImplementedError.
def default_tire_size
raise NotImplementedError
end
# subclasses may override
# This is the Hook Message instead of using a super
def post_initialize(args)
nil
end
# This is the Hook Message instead of using a super
def local_spares
{}
end
def default_chain
'10-speed'
end
end
Then the subclass will be created with the following:
class RoadBike < Bicycle
attr_reader :tape_color
def post_initialize(args)
@tape_color = args[:tape_color]
end
def local_spares
{tape_color: tape_color}
end
def default_tire_size
'23'
end
end
class MountainBike < Bicycle
attr_reader :front_shock, :rear_shock
def post_initialize(args)
@front_shock = args[:front_shock]
@rear_shock = args[:rear_shock]
end
def local_spares
{rear_shock: rear_shock}
end
def default_tire_size
'2.1'
end
end
and adding a new variants will be much easier like this:
class RecumbentBike < Bicycle
attr_reader :flag
def post_initialize(args)
@flag = args[:flag]
end
def local_spares
{flag: flag}
end
def default_chain
'9-speed'
end
def default_tire_size
'28'
end
end
bent = RecumbentBike.new(flag: 'tall and orange')
bent.spares
# -> {:tire_size => "28",
# :chain => "10-speed",
# :flag => "tall and orange"}