Inheritance

What are you going to learn?

  • Understand what it is inheritance
  • Identify when to use inheritance
  • Understand how inheritance works in Ruby

What is Inheritance?

Inheritance is a way to define classes that will share their behavior with multiple classes. You can easily reduce the code duplication by defining what is called a Parent class that can be used across multiple subclasses or child-classes.

Inheritance is, at its core, a mechanism for automatic message delegation. It defines a forwarding path for not-understood messages.

In simple words:

  • The class being inherited from is called the parent or superclass
  • A class can only inherit from one parent. This is not the same to other programming languages.
  • Any number of classes can inherit from a single superclass
  • When a class inherits from another class, it has access to all of the methods and instance variables from the superclass
  • The inheriting class is called the child or subclass
  • A subclass parent's parent and so on, is called ancestor

This is how it looks with Ruby code:

# This is the superclass or parent class
class Animal
end

# This is a child class
class Cow < Animal
end

# This is a child class
class Duck < Animal
end

In the code shown above, < Animal tells Ruby that any class with this snippet will inherit behavior from the Animal superclass. This is not the same as a module, even though it kind of seems the same. Remember a module also defines namespaces or context.

Whena applying inheritance take this into consideration:

  • Inheritance is for specialization, NOT for sharing code.
  • You should never inherit from a concrete Class. Always inherit from abstract Classes.

Here is a practical application from Sandi Metz:

Some of [a] bicycle’s behavior applies to all bicycles, some only to road bikes, and some only to mountain bikes. This single class contains several different, but related, types. This is the exact problem that inheritance solves; that of highly related types that share common behavior but differ along some dimension.

Subclasses

We already saw how a subclass looks like, by just adding the < ParentClass to the class definition, and we are ready to go. But's let's build a more comprehensive example on how this works.

class Pokemon
  attr_reader :name

  def initialize(name = "")
    @name = name
  end
end

We inherit the class Pokemon to the Pikachu subclass

class Pikachu < Pokemon
end

pikachu = Pikachu.new("Sparky")
pikachu.name

As you can see, we did not define the constructor method, nor the name attribute. The pikachu object responded to that because is inheriting all that from Pokemon. The advantage here is that we can still add methods to the Pikcahu class.

Unlike modules, the methods being inherited are not passed down to the subclass, but are looked up or delegated to the parent class.

Super

There will be a time when you want to extend parts of the behavior you are inheriting, this is when super comes into play. For example if you call the super method inside a subclass initialize method, it will call the superclass's initialize.

For example:

# pokemon.rb
class Pokemon
  attr_reader :name

  def initialize(name = "")
    @name = name
  end
end

# pikachu.rb
class Pikachu < Pokemon
  def initialize(name, sound)
    super(name)
    @sound = "pika pika"
  end
end

The call super will invoke the initialize method from the Pokemon class, which will set the @name variable. You need to respect the super arguments when it applies, this means that if the parent class expect to receive 1 parameter, you must add it.

Overrding Methods

As stated in the class lesson, we saw how we can override methods from built-in Ruby classes. This works the same while inheriting from a class. Let's say we have a make_sound method on the Pokemon class that the Pikachu class will change:

# pokemon.rb
class Pokemon
  attr_reader :name

  def initialize(name = "")
    @name = name
  end

  def make_sound
    ""
  end
end

# pikachu.rb
class Pikachu < Pokemon
  def initialize(name, sound)
    super(name)
    @sound = "pika pika"
  end

  def make_sound
    @sound
  end
end

This will totally override the method, without using any of the superclass implementation. You could use the super clause if you need it.

Properly Applying Inheritance

Subclasses are everything their Superclasses are, plus more. Any object that expect Bicycle should be able to interact with a Mountain Bike in blissful ignorance of its actual Class.

Two things are required for inheritance to work:

  • There is a generalization-specialization relationship in the objects you are modeling.
  • Correct coding techniques are used.

Exercises

Remember we have provided a repository with a bunch of exercises for you to complete. You can find it here

You can finde them under /ruby-exercises/Module1/inheritance.

Additional Resources