Welcome back! In this part of our Ruby Class Basics Review, we’ll dive into inheritance, a core concept in Ruby’s object-oriented programming (OOP). Inheritance enables code-sharing between classes, making our code more efficient and easier to read.
This lesson covers how inheritance works in Ruby, including attribute and method inheritance, along with the super
keyword to access superclass functionality. Ready to dive in? Let’s get started!
Inheritance in Ruby allows us to create a subclass that inherits attributes and methods from a superclass. This approach is especially useful when multiple classes share common features or behaviors.
Let’s explore with a superclass named Vehicle
and a subclass named Car
:
Ruby1# Define the superclass 'Vehicle' 2class Vehicle 3 # Initialize the Vehicle with color and brand attributes 4 def initialize(color, brand) 5 @color = color 6 @brand = brand 7 end 8end 9 10# Define the subclass 'Car', inheriting from 'Vehicle' 11class Car < Vehicle 12 def initialize(color, brand, doors) 13 # Use 'super' to call the superclass's initialize method 14 super(color, brand) 15 @doors = doors 16 end 17end
In this example, Car
inherits properties from Vehicle
. Ruby supports several types of inheritance, but here we’re focusing on single inheritance, where a subclass has a single superclass.
With attribute inheritance, a subclass can inherit instance variables from its superclass.
Ruby1class Artist 2 attr_reader :name # Getter for @name 3 4 def initialize(name) 5 @name = name # Superclass's attribute 6 end 7end 8 9class Musician < Artist 10 attr_reader :instrument # Getter for @instrument 11 12 def initialize(name, instrument) 13 super(name) # Inheriting superclass's attribute 14 @instrument = instrument # Subclass's own attribute 15 end 16end 17 18john = Musician.new('John Lennon', 'Guitar') 19puts john.name # Output: John Lennon 20puts john.instrument # Output: Guitar
The Musician
class inherits the @name
attribute from Artist
, while also introducing its own attribute, @instrument
. Getters can be conveniently defined using attr_reader
to access instance variables of each class.
Similar to attributes, method inheritance lets a subclass use methods defined in its superclass without redefining them.
In the example below, the Car
class inherits the start
method from the Vehicle
class:
Ruby1class Vehicle 2 def initialize(brand) 3 @brand = brand 4 end 5 6 def start 7 puts "The #{@brand} is starting." 8 end 9end 10 11class Car < Vehicle 12 # No new methods or attributes added here 13end 14 15my_car = Car.new('BMW') 16my_car.start # Output: The BMW is starting.
In this case, Car
can use the start
method inherited from Vehicle
.
The super
keyword is essential in Ruby inheritance, allowing a subclass to call methods from its superclass. super
is especially useful in method overrides or in initialize
methods to ensure that superclass functionality is included in the subclass.
Here’s how super
can be used to call an overridden method in the superclass and combine it with new behavior in the subclass:
Ruby1class Vehicle 2 def start 3 "Vehicle is starting..." 4 end 5end 6 7class Car < Vehicle 8 def start 9 "#{super} Beep! Beep!" 10 end 11end 12 13my_car = Car.new 14puts my_car.start # Output: Vehicle is starting... Beep! Beep!
Similarly, in initialize
, super
allows us to ensure that both superclass and subclass attributes are set up properly:
Ruby1class ParentClass 2 attr_reader :value # Getter for @value 3 4 def initialize(value) 5 @value = value 6 end 7end 8 9class ChildClass < ParentClass 10 attr_reader :additional_value # Getter for @additional_value 11 12 def initialize(value, additional_value) 13 super(value) # Calls superclass's initialize 14 @additional_value = additional_value 15 end 16end 17 18child = ChildClass.new("value", "additional_value") 19puts child.value # Output: value 20puts child.additional_value # Output: additional_value
In both examples, super
allows the subclass to build upon the superclass’s logic, resulting in flexible and reusable code.
In Ruby, self.class
allows instance methods to dynamically call class methods or access shared data. For example, we can track the total number of books checked out in a library system:
Ruby1class Library 2 @books_checked_out = 0 3 4 class << self 5 # Defines class-level methods for shared behavior 6 attr_reader :books_checked_out 7 8 def checkout_book 9 @books_checked_out ||= 0 # Initialize @books_checked_out if it's nil 10 @books_checked_out += 1 11 end 12 end 13end 14 15class LibraryMember < Library 16 def checkout 17 self.class.checkout_book 18 "A book has been checked out. Total checked out: #{self.class.books_checked_out}" 19 end 20end 21 22member = LibraryMember.new 23puts member.checkout # Output: A book has been checked out. Total checked out: 1 24 25another_member = LibraryMember.new 26puts another_member.checkout # Output: A book has been checked out. Total checked out: 2
In this example:
@books_checked_out
is a class instance variable, storing data shared among all instances of theLibrary
class.class << self
opens the singleton class, allowing us to define class-level methods likebooks_checked_out
andcheckout_book
in a grouped and organized manner.- The
checkout
method inLibraryMember
usesself.class
to dynamically call class methods (checkout_book
andbooks_checked_out
) on theLibrary
class.
This design ensures shared data is managed centrally while allowing dynamic access and modifications through instance methods.
In this lesson, we explored inheritance in Ruby, covering both attribute and method inheritance. We also discussed how the super
keyword allows subclasses to access superclass methods for extending or combining behaviors. Practicing inheritance will help you write more efficient, organized, and modular Ruby code. Get ready to apply these concepts in some hands-on exercises to deepen your understanding!