Inheritance
Inheritance is a core concept in object-oriented design. For an overview of object-oriented design, have a look at Object-Oriented Design Basics: Objects, Classes and Inheritance.
Inheritance is the ability of one class (a “subclass”) to use the state and behaviors of another class (a “superclass,” from which it “inherits”). For example, dogs and cats have specialized behaviors of their own. But they also have behavior that is common to all animals. This generalized behavior can be modeled in a superclass from which both animals inherit, so they don’t have to provide repetitive implementations of the behavior.
For example, an Animal
class might have a #sleep
method. Then, the classes that inherit from Animal
will have specialized behaviors of their own:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Animal def sleep puts "Rock a bye baby ... " end end class Dog < Animal def bark puts 'Dog barks' end end class Cat < Animal def meow puts 'Cat meows' end end spot = Dog.new sylvester = Cat.new spot.bark # => Dog barks spot.sleep # => Rock a bye baby ... sylvester.meow # => Cat meows sylvester.sleep # => Rock a bye baby ... |
Each instance has specialized behavior of its own, while sharing Animal
‘s methods as well. When Dog
or Cat
instances call the #sleep method, the call will be passed to the Animal superclass, which will then execute its #sleep method.
Semantically, inheritance defines an “is a” relationship: a subclass “is” a superclass from which it inherits. For example, if a Dog
class inherits from an Animal
class, the dog “is an” animal.
Multiple Inheritance Vs. Single Inheritance
Multiple inheritance means a class can inherit from any number of other classes, i.e. a class can have any number of superclasses. Single inheritance means a class can have no more than one superclass.
The pros and cons of multiple inheritance as compared to single inheritance are much discussed among computer scientists. Multiple inheritance gives rise to certain ambiguities that single inheritance doesn’t. However, single inheritance, since it requires that an object can only “be” one thing, has its limitations.
Ruby, along with many other popular programming languages such as Java and C#, does not permit multiple inheritance.
Here’s why multiple inheritance can be a problem. Suppose we have some animals. Some can fly, some can’t. We might have this:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Animal end class Bird < Animal end class Fish < Animal end class Whale < Animal end |
Now, how do we make our birds fly, without letting whales fly too? We could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Animal end class FlyingAnimal < Animal end class Bird < FlyingAnimal end class Fish < Animal end class Whale < Animal end |
Now, what if we want fish and whales to swim? We’ll need a SwimmingAnimal
class. So now what about flying fish, which can both fly and swim? Ruby won’t let FlyingFish
inherit both FlyingAnimal
and SwimmingAnimal
. So what do we do? And how about ducks? Don’t they have to fly and swim too? Now things are just getting unwieldy.
At some point, we would like to have multiple inheritance. But that’s a problem too: if a class can inherit two different classes, there isn’t a simple way to resolve name collisions — if I have two superclasses with the same method name, which one do I use when the subclass calls the method? That’s why Ruby won’t let us do it.
Mixins
So, how do we make our birds fly, and our fish and whales swim? How do we let the ones that can do both do both?
Ruby solves these problems with mixins, in the form of including modules. So, to make an animal both fly and swim, we could do something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
module Flyable def fly end end module Swimmable def swim end end class Animal end class Bird < Animal include Flyable end class Fish < Animal include Swimmable end class Whale < Animal include Swimmable end |
This way, we can easily handle flying fish as well, simply by inheriting Fish
and mixing in Flyable
(Swimmable
doesn’t need to be mixed in since Fish
has already taken care of that). We can handle ducks in similar fashion, by inheriting Bird
and mixing in Swimmable
:
1 2 3 4 5 6 7 8 9 10 11 |
class FlyingFish < Fish include Flyable end class Duck < Bird include Swimmable end #etc. |
Of course, the name collision issue can come up with mixins, too, but Ruby easily resolves this by specifying a method lookup path. The lookup path rules state that if two modules are included in a class, and have a method of the same name, the module which is included last (i.e. the one included lower in the code) is the one whose method will be used.
Other problems with multiple inheritance
Suppose Ruby allowed multiple inheritance. Consider these classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Animal def sleep puts 'Animal sleeps...' end end class FlyingAnimal < Animal def fly end end class SwimmingAnimal < Animal def swim end end class Duck < FlyingAnimal, SwimmingAnimal # not really allowed end donald = Duck.new() donald.sleep() |
The line donald.sleep()
has an ambiguity: is it calling the sleep
method via the FlyingAnimal
or the SwimmingAnimal
class? At a high level, it doesn’t seem to do either; it just calls the Animal
class’s sleep
method. But at the level of what pointers get assigned to where when classes get instantiated, there’s an ambiguity. The Duck
class has a pointer to a FlyingAnimal
instance and a SwimmingAnimal
instance, and each of these instances have a pointer to their own Animal
instance. Two different Animal
instances, in other words. So, when sleep
is invoked by a Duck
instance, which of these two instances does it go through to invoke it?
There are various ways to resolve this ambiguity. One of these is to disallow multiple inheritance and support mixins instead, as Ruby does. (This Wikepedia article has more information.)
There are, of course, other topics in the the multiple inheritance vs. single inheritance debate. I’ll let someone else write the book that goes into these in detail, but this should give an introduction, and perhaps enough information to get an idea of why Ruby doesn’t allow multiple inheritance.