OOAD

Object-Oriented Ruby: Inheritance, Multiple Inheritance and Why Ruby Doesn’t Allow It, and Mixins

posted in: OOAD, Ruby, Software Engineering | 0

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:

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:

Now, how do we make our birds fly, without letting whales fly too? We could do something like this:

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:

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:

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:

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.