Class inheritance is a fundamental OOP (Object-Oriented Programming) feature that helps you create a more specific & specialized version of any class.
Here’s an example:
Food -> Fruit -> Orange
There’s a relationship between these classes!
We can say that an orange is a fruit, but fruits are also food.
The parent class (also called super class or base class) is always more generic than the subclasses.
Fruit
(more generic) is the parent class of Orange
(more specific).
In Ruby it looks like this:
class Food end class Fruit < Food end class Orange < Fruit end
One of the implications of inheritance in Ruby is that every method & every constant defined on Food
will be available on Fruit
, and also on Orange
.
The methods are passed down from the base class, but not the other way around.
What’s The Purpose of Inheritance?
You may think that creating an object hierarchy provides some kind of “feel good” organization for your code, but there is a lot more to it.
Inheritance is used to create a different version of a parent class which is fit for a particular purpose. Maybe it needs some extra features or methods that wouldn’t make sense in the parent class.
Here’s an example:
All fruits have a color, a weight, and a name.
Some fruits may have special characteristics that aren’t shared with other fruits, so you create a new class that inherits the characteristics of ALL fruits (color, weight, etc.) & then you add the special characteristic.
That’s what I mean by specialization.
Another example:
You have a class that writes to a database, but you want another version of the class (perhaps for debugging purposes) which logs all the database operations.
In this case, you want the decorator pattern.
You can read a detailed explanation on this article, but the basic idea is that you use inheritance to wrap another class & then add something new to it.
Without having to change the original!
Inheritance in The Real World
Ok.
We have learned about inheritance, but did you know that you’re using it every day as a Ruby developer?
Ruby itself uses inheritance to enable methods like:
puts
class
super
This is because all Ruby objects inherit from the Object
class by default.
So if you create a class like this:
class Apple end
Its parent class is Object
:
Apple.superclass # Object
That’s why you’re able to use methods like the ones mentioned above.
For example, when you call puts
Ruby looks for this method in your class.
Then:
- It looks for the method in one of the parent classes
- If not found, it starts from the object again & it’ll try to find
method_missing
- If not found, it will raise a
NoMethodError
, orNameError
if the method was called without an explicit object (a.size
vssize
, wherea
is the explicit object)
You can find more examples of inheritance in Rails.
Right here:
class ApplicationController < ActionController::Base end
Controller:
class SessionsController < ApplicationController end
Model:
class Comment < ApplicationRecord belongs_to :article end
Inheritance is everywhere in Ruby, but sometimes it isn’t the right solution.
Composition: An Alternative to Inheritance
Inheritance has certain limitations.
For example:
You want to build a computer from parts.
We say that the computer has parts, but the individual parts aren’t computers by themselves.
If you take them apart they can’t do their function.
We need something else…
We need composition!
Composition builds classes where different parts come together to perform a function.
Just like a computer.
Here’s an example of composition in action:
class Computer def initialize(memory, disk, cpu) @memory = memory @disk = disk @cpu = cpu end end
The computer is given the parts it needs to work.
This is composition.
The Liskov Substitution Principle
Inheritance is powerful when used in the right situations.
But like all tools it can be abused!
In fact, there is a popular quote from the original Design Patterns book that goes like this:
“Prefer composition over inheritance.”
Design Patterns: Elements of Reusable Object-Oriented Software
To make sure you’re using inheritance correctly there is one principle you can follow, the L
from SOLID.
It stands for “Liskov Substitution Principle”.
This says that your subclasses must be able to be used in place of your base class.
In other words:
If you inherit from Fruit
& the color
is a string, you don’t want to change the color
to return a symbol in a subclass.
Example:
Users of Fruit
depend on color
returning a string.
class Fruit def color "orange" end end
This breaks LSP:
class Orange < Fruit def color :orange end end
If you change color
to return a symbol, then you can’t replace a Fruit
object with an Orange
.
Why?
Because if you call a method like split
on a symbol you’ll get an error.
It’s a method that symbols don’t have.
But strings do.
Another red flag is when your subclass is not a true specialization of the parent class & you’re just using the parent class to share utility methods.
Summary
You’ve learned about inheritance & composition in Ruby!
You can use inheritance to create a specialized version of a parent class, and composition to put together components into a whole. Remember to follow the LSP
principle if you don’t want to make a mess.
Now you can write better Object-Oriented Code 🙂
Thanks for reading.