Jun 24, 2015
Table of contents:
One of the fundamental aspects of Object-oriented programming is inheritance.
Inheritance is where an object inherits from another object. This means it acquires the same properties and methods of it’s parent.
This is an important concept for a lot of reasons. However, I think inheritance is often misused by newbie programmers who jump at the opportunity to DRY up their code, without thinking about the implications of forced inheritance.
In today’s tutorial we will be looking at inheritance in Ruby.
Object inheritance is where an object inherits the properties and methods of a parent object. This means the child object will automatically have those methods, without having to implement them.
When you invoke a method on an object, Ruby will first check the class to see if it has that particular method.
If the class does not have the method, Ruby will walk up the chain of ancestors to find an implementation of the method.
Ruby is a single inheritance language and so an object can only inherit from a single parent.
As we saw in Writing Ruby Classes, if you do not specify the parent of a class, the class will automatically extend from Object
.
As I mentioned in the introduction to this post, inheritance is a very important aspect of object-oriented programming, but it is also something that I think is misused.
Inheritance suggest specialisation, and so an object should only inherit from another object when the object is a type of the parent.
For example, if we had a generic Car
class it would make sense to have a child Aventador
because that is a specific type of car.
However, I often see examples of inheritance that is only implemented to prevent repetition of common methods. Keeping your code DRY is important, but it is often not the most important thing.
Inheritance is a strong relationship between two objects. Semantically, it means a great deal to inherit from an object.
In my opinion you are best off avoiding inheritance unless it is a very strong match. In other words, only ever use inheritance when something is definitely a specialisation of it’s parent.
Unnecessary inheritance can cause a world of pain when you inevitably try to evolve the forced child classes in different directions.
To fully understand inheritance, it’s best to look at some code.
First we have the Car
class:
class Car
end
If you’re coming from a language like PHP, the first thing to notice is that there is no concept of an abstract
class, we simply use a normal class as the parent.
To inherit from a parent class, you create a class as normal, but with <
after the class name:
class Aventador < Car
end
To check to see what the parent class of a class is, you can call the superclass
Class Method:
Aventador.superclass
# => Car
You can also check the entire ancestor chain with the ancestors
Class Method:
Aventador.ancestors
# => [Aventador, Car, Object, Kernel, BasicObject]
So as you can see, the parent of the Car
class is the Object
class.
Now when we call a method on the Aventador
class, if the method is not available it will check the Car
class. If the Car
class does not have the method it will check the Object
class and so on up the ancestor chain.
An important part of inheritance is the ability to override methods. For example:
class Car
def top_gear
5
end
end
class Aventador < Car
def top_gear
7
end
end
By default the Car
has a top gear of 5, but the Aventador has a top gear of 7.
We can see this in action by creating a new instance in IRB:
car = Aventador.new
# => #<Aventador:0x007fa02d059338>
puts car.top_gear
7
This works because Ruby will first check the Aventador
class for the top_gear
method. Because Ruby finds the method in the child class, it invokes the method and stops looking.
If the child class does not have the method, Ruby will work it’s way up the chain until it finds an implementation:
class Car
def start_engine
true
end
def top_gear
5
end
end
class Aventador < Car
def top_gear
7
end
end
Now if we try to invoke the start_engine
method on the Aventador
object, the method will be inherited:
car = Aventador.new
# => #<Aventador:0x007fa02b3cf758>
car.start_engine
# => true
Sometimes you need to pass arguments to the parent’s version of the method. For example, perhaps the parent’s initialize
method requires an argument:
class Car
attr_accessor :colour
def initialize(colour)
@colour = colour
end
end
To pass arguments to the parent method, you can call the super
method:
class Aventador < Car
def initialize(colour)
super
end
end
By default if you don’t pass any arguments to the super
method, all of the arguments that were passed to the calling methods will be passed on.
If you only want to pass certain arguments to the super
method you will need to supply them as part of the call to super
Inheritance is a very important concept in Object-oriented Programming. When an object inherits from another object it means a lot semantically.
Inheritance suggests specialisation, and so it should only be used when the child object is a specialisation of the parent object.
However inheritance is often misused as a crutch to avoid repetition. I’ve seen a lot of n00bie code that goes nuts with inheritance just to keep things DRY.
This inevitably leads to code that is hard to maintain!
Sometimes it is better to repeat yourself as an object should never inherit from a parent just to save a few lines of code.
Learning when you should use inheritance and when you should avoid it is pretty situational and really will only come with experience. Once you feel the pain of forced inheritance you will begin to see that inheritance isn’t all that.