Understanding Inheritance in Ruby

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 Object.

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 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 the ```
after the class name:

class Aventador < Car<br></br>
end<br></br>

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.

Overriding methods

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  

Calling Super

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

Conclusion

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.

Understanding Inheritance in Ruby
Share this