Class and Interface Inheritance in Ruby

Class and Interface Inheritance in Ruby

Inheritance in object oriented programming is a concept that refers to the ability of classes to inherit behaviors from other classes. It is not, however, a two-way inheritance. In other words, one class, a subclass, inherits behaviors from another class, a superclass; the superclass does not inherit behaviors from the subclass.

In Ruby there are two types of inheritance: class inheritance and interface inheritance. Both are equally important and together widen our ability to give functionality to objects while adding the benefits of simplicity, flexibility and scalability to our code.

Class Inheritance

Class inheritance is created when common behaviors (methods) are extracted to a superclass from which one or more subclasses inherit those behaviors. This means that an object of the subclass can invoke methods that are defined in the superclass without the method needing to be defined in the subclass. We can think of a superclass as a library of methods that, when inherited, extends the functionality of the subclass; in other words, a subclass has not only the functionality defined within itself but the functionality of its superclass as well. The syntax to indicate class inheritance is simple: when defining a class we use the < character after the class name, followed by the name of the superclass.

For example, let's say we have a restaurant where we want to define an Employee superclass with multiple subclasses of different types of employees. Here is how we would indicate that a class Host is a subclass of the superclass Employee:

class Employee
  def clock_in
    "I clocked in!"
  end
end

class Host < Employee; end

To see how inheritance is implemented in the code above, we can simply invoke the clocked_in method from the Employee class on an object of the Host class.

class Employee
  def clock_in
    "I clocked in!"
  end
end

class Host < Employee; end

host = Host.new

puts host.clock_in
# => "I clocked in!"

To further demonstrate the implementation and benefits of inheritance we can add some complexity to our example.

class Employee
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee; end

class Server < Employee; end

class Busser < Employee; end

class Cook < Employee; end

host = Host.new
puts host.clock_in
# => "I clocked in!"
puts host.clock_out
# => "I clocked out!"

server = Server.new
puts server.clock_in
# => "I clocked in!"
puts server.clock_out
# => "I clocked out!"

busser = Busser.new
puts busser.clock_in
# => "I clocked in!"
puts busser.clock_out
# => "I clocked out!"

cook = Cook.new
puts cook.clock_in
# => "I clocked in!"
puts cook.clock_out
# => "I clocked out!"

As mentioned previously, inheritance allows us to extract common behaviors to a single superclass. This means that we can write code once and then reuse that same code in as many subclasses as needed, all without having to rewrite the same code over and over again. We see this in the above code with the clock_in and clock_out methods from the Employee superclass: we've defined the methods once and can invoke them from any of the subclasses we've defined after it. Without inheritance we would have to define the clock_in and clock_out methods in every class where those behaviors are needed.

Additionally, with inheritance we can still define more fine-tuned behaviors in a subclass that may not be applicable to all subclasses of the superclass from which the subclass inherits. To further elaborate on our example above, while every employee has the ability to clock in and clock out when starting and ending a shift, each employee also has behaviors specific to the employee’s role. For example, while a server can serve guests, a host cannot.

class Employee
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee
  def seat_guest
    "Guest has been seated."
  end
end

class Server < Employee
  def serve_guest
    "Guest has been served."
  end
end

class Busser < Employee
  def bus_table
    "Table has been cleared."
  end
end

class Cook < Employee
  def cook_dish
    "Dish has been cooked."
  end
end

Structuring code in classes that inherit behaviors from a common superclass enables great flexibility in the code we write. With such flexibility we can easily reuse the common behaviors that we’ve extracted into superclasses while still defining more specific behaviors in subclasses where needed. For example, we can define as many subclasses of the Employee class as needed — executives, managers, marketers, dishwashers and so on — without having to rewrite and repeat any of the behaviors inherited from the Employee superclass.

But what if we have common behaviors shared between multiple but not all subclasses? For example, let’s say that servers are able to perform the same functions that a host does— taking down the guest’s name, seating the guest and giving the guest a menu. Does this mean that we need to define all of these behaviors in both classes? Or is there a way to extract those behaviors to common, inheritable location as well?

The second type of inheritance — interface inheritance — allows us to do just that.

Interface Inheritance

Before we dive into a discussion of interface inheritance, let’s continue our thought experiment with our make-believe restaurant. To do so, let’s postulate that an object of a Server class is able to perform many of the same functions as an object of the Host class, specifically functions related to seating a guest. For example, both hosts and servers are able to take down a guest's name, seat the guest at a table, give the guest a menu and inform the guest of the daily special when the guest is seated. However, because these are not behaviors that are shared among all subclasses of the Employee superclass, we do not want these behaviors to be defined in that class and then inherited by subclasses for whom those behaviors would not be appropriate (like cooks and bussers).

Let’s modify our code to give our Host and Server classes these new behaviors.

class Employee
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee
  def take_guest_name
    # implementation
  end

  def seat_guest
    # implementation
  end

  def give_guest_menu
    # implementation
  end

  def recite_daily_special
    # implementation
  end
end

class Server < Employee
  def take_guest_name
    # implementation
  end

  def seat_guest
    # implementation
  end

  def give_guest_menu
    # implementation
  end

  def recite_daily_special
    # implementation
  end

  def serve_guest
    "Guest has been served."
  end
end

class Busser < Employee
  def bus_table
    "Table has been cleared."
  end
end

class Cook < Employee
  def cook_dish
    "Dish has been cooked."
  end
end

That’s a lot of repetitive code, which leads us to ask: is there a way to extract that code to a common location, from which the Host and Server classes can inherit those behaviors in the way we did with class inheritance from the Employee class? Interface inheritance enables us to do just that. The difference though is that, rather than inheriting the behaviors from a superclass, these behaviors can be inherited from a module.

A module is similar to a class in that it is a collection of related behaviors; however, a key difference is that an object cannot be instantiated from a module. While a class provides a blueprint for the state and behaviors of objects instantiated from the class, a module simply adds functionality to a class where that module has been included. As a collection of behaviors, a module can be “mixed in" to a class by invoking the include method, followed by the name of the module. When a module is used this way it is called, appropriately, a mixin module. Modules are defined like classes except by using the module keyword instead.

Let’s go back to our example above and extract the methods common to both the Host and Server classes to a module called Hostable [1].

module Hostable
  def take_guest_name
    # implementation
    puts 'Guest name taken...'
  end

  def seat_guest
    # implementation
    puts 'Guest seated...'
  end

  def give_guest_menu
    # implementation
    puts 'Menu given to guest...'
  end

  def recite_daily_special
    # implementation
    puts 'Daily special recited...'
  end
end

class Employee
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee
  include Hostable
end

class Server < Employee
  include Hostable

  def serve_guest
    "Guest has been served."
  end
end

class Busser < Employee
  def bus_table
    "Table has been cleared."
  end
end

class Cook < Employee
  def cook_dish
    "Dish has been cooked."
  end
end

host = Host.new
host.seat_guest
# => 'Guest seated...'

server = Server.new
server.seat_guest
# => 'Guest seated...'

As you can see in our example above, we’ve deduplicated all of the functionality related to seating a guest and extracted it to a single module named Hostable . We then gave access to that functionality in our Host and Server classes by mixing in the Hostable module via invocation of the include method. In fact, we can include as many modules as may be appropriate for the class in question. For example, let's say that both hosts and servers, but not bussers and cooks, can sing a birthday song when a guest visits the restaurant on her birthday. Rather than writing a sing_birthday_song method in both the Host and Server classes, we can define that method in a Singable module and then include that module in the two classes, just as we did with the Hostable module.

module Hostable
  # code omitted for brevity
end

module Singable
  def sing_birthday_song
    # implementation
  end
end

class Employee
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee
  include Hostable
  include Singable
end

class Server < Employee
  include Hostable
  include Singable

  def serve_guest
    "Guest has been served."
  end
end

Structuring inheritance through modules gives Ruby classes the ability to inherit from multiple places. While some programming languages have multiple inheritance, which means that a subclass can inherit from more than one superclass, Ruby is strictly a single inheritance language. However, through the use of modules and interface inheritance, Ruby can mimic multiple inheritance.

Class Inheritance vs. Interface Inheritance

So far we have seen both class and interface inheritance in practical examples, but it may not yet be clear as to when one might use one or the other to structure behaviors and the classes that inherit them.

You’ll recall from earlier in our discussion that inheritance is about grouping common behaviors into a single place from which multiple classes can inherit these behaviors, with that single place being either a superclass or a module. In both cases, the goal is to group common behaviors that can be defined once and then reused as many times as needed without duplicating code unnecessarily. Yet we want to group behaviors in such a way that the behaviors being inherited are appropriate to the classes that inherit them. In doing so, we can write code that is more flexible, highly scalable and much easier to maintain.

In general, if there is a ‘is-a’ relationship between classes, class inheritance is typically the best place to start. Class inheritance allows us to structure classes and behaviors into logical hierarchies, often in a way that models the hierarchies we see in the real world. Referring back to the examples we used previously, a host ‘is a’ employee, as is a server, a busser and a cook. And, naturally, as members of a more general class of Employee , there will be behaviors that each of them have in common, such as clocking in and out at the start and end of a shift.

However, there are often exceptions to this sort of hierarchical modeling such that there may be behaviors that some subclasses have in common but not all. We saw this with the Host and Server classes, which were both able to perform the functions of a host (i.e., seating guests). Since such behaviors are common only to them and not the other subclasses of the Employee superclass, we should not define these behaviors in the Employee class, since this would result in subclasses (like bussers and cooks) inheriting behaviors which would not be appropriate for them to have.

The alternative of course is to define these methods in the individual subclasses that need them; but that’s not a very good alternative, as our code becomes unnecessarily repetitive. This is where interface inheritance through mixin modules comes in. When common behaviors don’t necessarily fit into the hierarchical models we’ve created through subclassing, and there is a ‘has-a’ relationship between these behaviors and the classes that need them, then interface inheritance would typically be the better choice in such a case. This is exactly what we saw with the Host and Server classes and the Hostable module, where the behaviors didn't fit into the Employee superclass, and both the Host and Server classes "had” the behaviors in the Hostable module.

Chain of Inheritance and the Method Lookup Path

Even though Ruby is a language of single inheritance, where a class can have only one superclass, it is the case that a class also inherits the behaviors that its superclass inherits. That’s a bit of a mouthful, but it simply means that there is always a chain of inheritance involved when subclassing one class from another. Like water running from the top to the bottom of a chain, the water (behaviors) runs through any links (classes) in the chain that are created through subclassing additional classes until it reaches the last link in the sequence.

For our restaurant thought experiment we created an Employee superclass with a number of employee type subclasses. To demonstrate this chain of inheritance, we're now going to define a Person superclass from which the Employee class subclasses. Additionally, we will also create a class Guest , which also subclasses from the Person class, in addition to Adult and Child classes that subclass from Guest .

class Person
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

# classes related to a Guest
class Guest < Person; end

class Adult < Guest; end

class Child < Guest; end

# classes related to an Employee
class Employee < Person; end

class Host < Employee; end

class Server < Employee; end

class Busser < Employee; end

class Cook < Employee; end

To make it easier to see, let’s extract the structure we’ve created between our classes.

Person < Guest < Adult
Person < Guest < Child
Person < Employee < Host
Person < Employee < Server
Person < Employee < Busser
Person < Employee < Cook

With this structure in place, the final subclasses in the chain we’ve created inherit behaviors that are defined at the top of the chain as well as any classes in between. For example, the Host class inherits the behaviors in the Person class because these behaviors are inherited in the Employee class, and the Host class inherits behaviors from the Employee class.

To see this chain of inheritance in practice we’ll create an object from the Adult class and then invoke the name method that's defined in the Person class.

class Person
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

# classes related to a Guest
class Guest < Person; end

class Adult < Guest; end

class Child < Guest; end

# classes related to an Employee
class Employee < Person; end

class Host < Employee; end

class Server < Employee; end

class Busser < Employee; end

class Cook < Employee; end

adult = Adult.new('Nancy')
puts adult.name
# => 'Nancy'

As we saw in our discussion of class inheritance, we are able to invoke the name method that's defined in the Person class on an object of the Adult class, even though the name method is defined in neither the Adult class nor in the Guest class. This, again, is inheritance at work.

To illustrate the chain of inheritance further, consider the commonly used object_id method. This method can be invoked on any Ruby object, even though this method isn't defined in any of the classes of the objects upon which we commonly invoke this method (e.g., strings, integers and arrays). This is because the object_id method is an instance method of the Object class, and all Ruby objects ultimately inherit from the Object class in the chain of inheritance.

For example, if we were to invoke the object_id method on an object of the Cook class, the chain of inheritance would look like this:

Object < Person < Employee < Cook

In Ruby there is a specific mechanism underlying this chain of inheritance called the method lookup path. This is the means by which Ruby locates a method definition in order to execute a method invocation of the same name. In more pedestrian terms, this is the answer to the question: how does Ruby know where to find the definition of a method when it's invoked? We can think of the method lookup path exactly as the name implies: the path that Ruby takes through the chain of inheritance to find a method definition for an invoked method.

For any method invocation, the general sequence that Ruby follows to find a matching method definition looks like this:

Class (of the object upon which the method was invoked)
  ↓
Module(s)
  ↓
Superclass
  ↓
[repeat w/the Superclass now in the beginning position taken by Class]

As you can see in the outline above, Ruby first looks in the class of the object upon which the method was invoked. If Ruby doesn’t find a matching method definition there, Ruby then looks in included modules, if any. If there are two or more modules included in the class, Ruby looks in the most recently included module first (which would be the last included module), then works its way backwards through any remaining modules. If a method definition still hasn’t been found, Ruby then looks in the class’s superclass, repeating the same cycle as many times as necessary. Once Ruby has exhausted all custom classes and modules without finding a matching method definition, then Ruby continues through the following classes and modules:

Object
  ↓
Kernel (module in the Object class)
  ↓
BasicObject

If Ruby is unable to find a matching method definition anywhere in the method lookup path, a NoMethodError is raised.

To see the method lookup path in practice, let’s consider the following code.

module Hostable
  def take_guest_name
    # implementation
    puts 'Guest name taken...'
  end

  def seat_guest
    # implementation
    puts 'Guest seated...'
  end

  def give_guest_menu
    # implementation
    puts 'Menu given to guest...'
  end

  def recite_daily_special
    # implementation
    puts 'Daily special recited...'
  end
end

module Singable
  def sing_birthday_song
    # implementation
  end
end

class Person
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class Employee < Person
  def clock_in
    "I clocked in!"
  end

  def clock_out
    "I clocked out!"
  end
end

class Host < Employee
  include Hostable
  include Singable
end

class Server < Employee
  include Hostable
  include Singable

  def serve_guest
    "Guest has been served."
  end
end

class Busser < Employee
  def bus_table
    "Table has been cleared."
  end
end

class Cook < Employee
  def cook_dish
    "Dish has been cooked."
  end
end

host = Host.new('Dave')
host.seat_guest
# => 'Guest seated...'

server = Server.new('Betsy')
server.name
# => 'Betsy'

In the above example we have created two objects, one of the Host class and one of the Server class. On our host object we have invoked the seat_guest method.

Upon invocation of the seat_guest method on host , here’s the method lookup path that Ruby uses to locate the matching method definition.

Host
  ↓
Singable
  ↓
Hostable

[seat_guest method definition found in Hostable module]

Had a seat_guest method not been found, Ruby would have exhausted the entire method lookup path before raising a NoMethodError.

Host
  ↓
Singable
  ↓
Hostable
  ↓
Employee
  ↓
Person
  ↓
Object
  ↓
Kernel
  ↓
BasicObject
  ↓
[NoMethodError]

Conclusion

To summarize, inheritance refers to the ability of one class to inherit the behaviors of another class, which allows us to extract common code to a single inheritable location. Because Ruby is a single inheritance language, which means that a class can only subclass from a single superclass, Ruby has two types of inheritance: class inheritance and interface inheritance. While class inheritance allows us to create hierarchical relationships between classes, it doesn’t always make sense for certain behaviors that are shared between only some subclasses to be inherited from the superclass. As a result, interface inheritance through mixin modules gives us the ability to mimic multiple inheritance. To execute a method invocation, Ruby uses the method lookup path to find the matching method definition.


  1. The common naming convention for mixin modules is to append the ‘-able’ suffix to the module name. ↩︎

Comments