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.
What is object inheritance?
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
When should you use inheritance?
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.
Working with inheritance in Ruby
To fully understand inheritance, it’s best to look at some code.
First we have the
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
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
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
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.