Understanding class inheritance in Ruby
Ruby is an object-oriented programming language, and like most object-oriented programming languages, Ruby uses classes for the purpose of data abstraction, encapsulation, polymorphism, and inheritance. The focus of this article is Ruby class inheritance.
What is a class?
A class is a means of creating objects which have similar attributes. It means that for classes to be created, there have to be objects with shared or related characteristics who need to be severally and distinctly classified.
Defining a class in Ruby is as simple as using the class
keyword and picking a name to identify it with. Just like defining a method you will close the class with the end
keyword comes last to indicate the end of a particular class.
Example 1
class MyClass
end
Classes are created to have single responsibilities. In order for a class to create objects to populate itself, it has to implement functionalities that pertain to the attributes(behaviour and state) of the objects to be created. These functionalities can be achieved through functions, better known as methods. Therefore, the class should have methods whose output achieves a particular purpose.
Functions/Methods
In object-oriented programming, there are typically two types of functions: class methods and instance methods.
Methods are vital to the class because all expressions of its state and behaviour are encoded in the method’s procedures and data variables. A method determines what attributes an object should have and the way it should behave. A method can have zero or more arguments depending on it’s definition.
Here is an example of a class with an instance method:
class MyClass
# instance method
def an_instance_method()
puts 'My Instance method implementation.'
end
end
Objects / Instances
An object is an “instance” of a class whose type is the class it belongs to. Objects are created at run time through the process of “instantiation” by invoking the class’ constructor method: new()
, which all Ruby classes have:
var_1 = MyClass.new()
Now that we’ve created an object var_1
for the class MyClass
, we now have a class, an instantiated object, and a method. To give functionality to the object, we have to call a method on it.
Note: The method an_instance_method
in our example does not have any parameters, so the object var_1
has to call the method any arguments.
var_1.an_intance_method() # => My instance method implementation.
Class Inheritance
Classes are related to each other when they have similar attributes. If there are obvious similarities in related classes, code repetition will happen.
Inheritance allows you to apply the concept of code reusability through inheritance. With class inheritance, a programmer can create a new class that inherits features from an existing class instead of writing those features over again.
A class becomes a “parent class” when another class (a “child class”) inherits some or all of it’s features. The pair is also known as the “super class” and “sub class”, or a “base class” and a “derived class”.
Let’s consider a class named Person
. We want our Person
class to only feature objects with attributes that pertain to people in the real sense of it. In that case the person object should have a name, have a designation, have a gender, and be capable of self introduction.
class Person
def introduction(name, designation, gender)
puts 'I am #{name}, a #{gender} #{designation}.'
end
end
# creating objects
person = Person.new()
person.introduction('Petera', 'supervisor', 'female')
# => I am Petera, a female supervisor
Now let’s create the Teacher
class, which will inherit from the Person
class:
class Teacher < Person
def portfolio(subject)
puts 'I teach #{subject}'
end
def student_marks(student_1, student_2, student_3)
puts 'Marks recorded: #{student_1}, #{student_2},and #{student_3}'
end
end
The Teacher
class inherits the methods from Person
because an object of the Teacher
, meaning an instance of the Teacher
class can also introduce itself, has a name, a designation, and gender. So instead of repeating any already existing code in the Teacher
class, you can take advantage of inheritance to reuse the code.
# creating objects
teacher = Teacher.new()
# calling a method from Person on the Teacher objects.
teacher.introduction('John', 'teacher', 'male')
# => I am John, a male teacher
# calling a method from Person on the Teacher objects.
teacher.portfolio('Biology')# => I teach Biology
teacher.student_marks(80, 60, 100)# => Marks recorded: 80, 60, 100
The Teacher
class re-uses the functionality defined in Person
, and only needs to implement code specific to a Teacher.
Next we introduce the Student
class, as a teacher’s role is to teach students.
class Student < Person
def subjects(subject_1, subject_2, subject_3)
puts 'I major in #{subject_1}, #{subject_2}, and #{subject_3} '
end
end
student = Student.new()
student.introduction('Jill', 'student', 'female')
# => I am Jill, a female student
The single inheritance rule
Ruby only supports a single class inheritance. Therefore, a child class(subclass or derived class) can only have one parent class(superclass or base class), and should only inherit from that class.
Although Ruby does not allow a single class to inherit from multiple classes, it offers a alternative means for classes to share reusable code through the concept of Modules and Mixins.
Modules and Mixins
A module is an object that has namespaces for variables, methods, and sometimes classes. It’s definition is very similar to that of the class, only that instead of a class keyword, you’ll have the module instead. Let’s create a School
module in a file school.rb
.
# school.rb
module School
TEACHERS = 50
STUDENTS = 100
def verify(id, designation)
puts 'The ID #{id},belongs to a verified #{designation}.'
end
def population()
puts 'A total of #{TEACHERS + STUDENTS} teachers and students'
end
end
Classes can inherit any code within a module as a mixin. For this to work, you have to require the module’s file inside the file defining your class, and then “include” the module within that class. There are no restrictions on the number of modules a class can get mixins from.
Here is how to mix in module objects in a class:
$LOAD_PATH << '.'
require 'school'
class Teacher < Person
def portfolio(subject)
puts 'I teach #{subject}'
end
def subject_marks(student_1, student_2, student_3)
puts 'Marks recorded: #{student_1}, #{student_2},and #{student_3}'
end
end
class Student < Person
include School
def subjects(subject_1, subject_2, subject_3)
puts 'I major in #{subject_1}, #{subject_2}, and #{subject_3} '
end
end
student = Student.new()
student.introduction('Jill', 'student', 'female')
student.verify('student_111', 'student')
student.population()
# The output
# => I am Jil, a female student
# => The ID student_111 belongs to a verified student.
# => A total of 150 teachers and students am Jil, a female student
Method Overriding
Method overriding is a feature in object-oriented programming that allows a subclass to bypass the implementation of a method in its superclass (also known as parent class). To achieve this, the subclass defines a method with the same name as the target method in the superclass, but yields a different or specialized outcome. Whenever an object of the subclass makes a call to that method, the superclass’ implementation of the method will be replaced by that of the subclass.
class Person
def motto
puts 'To lead is to serve'
end
end
class Student < Person
# A method to override the superclass method
def motto
puts 'We are tomorrow’s leaders'
end
end
student = Students.new()
student.motto # => We are tomorrow's leaders
Invoking Methods from the Super Class
When the super class and the subclass share a method with the same signature, a call to that method by an object of the subclass always overrides the method of the superclass. The way to access the parent’s version of the method is to invoke the super keyword.
If there are no parameters in the shared method, when super
or super()
is invoked, the logic of both methods are executed.
class Person
def motto
puts 'To lead is to serve'
end
end
class Student < Person
# A method to override the superclass method
def motto
super() # Invoking super would give the same result.
puts 'We are tomorrow’s leaders'
end
end
student = Student.new()
student.motto
# => To lead is to serve
# => We are tomorrow's leaders
If there are arguments in the superclass’ method, invoking super()
will execute those arguments. This is because super()
takes no arguments from the subclass.
class Person
def profile(id = 'teacher_001', name = 'James Bart')
puts 'This profile belongs to #{name}, with ID #{id}'
end
end
class Student < Person
# calling super()
def profile(id, name)
super()
puts 'We are tomorrow’s leaders'
end
end
person = Student.new()
person.profile('student_001', 'Lisa Green')
# => This profile belongs to James Bart, with ID teacher_001
# => We are tomorrow’s leaders
If in the same situation we invoke super
without brackets, the arguments from the subclass’ method are passed to the parent class to be executed.
class Person
def profile(id = 'teacher_001', name = 'James Bart')
puts 'This profile belongs to #{name}, with ID #{id}'
end
end
class Student < Person
# calling super()
def profile(id, name)
super
puts 'We are tomorrow’s leaders'
end
end
person = Student.new('student_001', 'Lisa Green')
person.profile('student_001', 'Lisa Green')
# => This profile belongs to Lisa Green, with ID student_001
# => We are tomorrow’s leaders
Note: One way to prevent the arguments of the subclass’ object from being passed to the superclass is by calling super() in it’s method. On the other hand, one sure way to impose the arguments of the subclass’ object on the superclass is by calling for example; super(id, name) in the subclass.
Conclusion
Class inheritance is a powerful feature in object oriented programming that prevents your program from having duplicated code. However, it should only be used sparingly when there are features to be shared among objects.