This example showcases a complete implementation of a Bicycle system using classical inheritance. It helps us understand the limitations of this approach, setting the stage for the transition to composition.
Note: This example is inspired by Sandi Metz's work in Practical Object-Oriented Design in Ruby (POODR).
class Bicycle 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 def default_tire_size raise NotImplementedError, "This #{self.class} cannot respond to: default_tire_size" end def default_chain "10-speed" end def post_initialize(args) nil end def local_spares {} end end class RoadBike < Bicycle 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 def post_initialize(args) @front_shock = args[:front_shock] @rear_shock = args[:rear_shock] end def local_spares { front_shock: @front_shock } end def default_tire_size "2.1" end end class RecumbentBike < Bicycle 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 road_bike = RoadBike.new(size: "M", tape_color: "red") mountain_bike = MountainBike.new(size: "L", front_shock: "Manitou", rear_shock: "Fox") recumbent_bike = RecumbentBike.new(size: "S", flag: "Tall") puts road_bike.spares puts mountain_bike.spares puts recumbent_bike.spares
This implementation demonstrates the rigid structure of inheritance. While functional, it often leads to duplication, tight coupling, and difficulty in extending behavior. In the next section, we’ll explore the challenges that arise from this approach.
Next: Challenges with Inheritance