So much repetition! Your new classes representing the different types of vehicles and animals are awesome, it’s true. But you’re having to copy instance methods from class to class. And the copies are starting to fall out of sync—some are fine, while others have bugs. Weren’t classes supposed to make code easier to maintain?
In this chapter, we’ll learn how to use inheritance to let your classes share methods. Fewer copies means fewer maintenance headaches!
Back at Got-A-Motor, Inc., the development team wants to try this “object-oriented programming” thing out for themselves. They’ve converted their old virtual test-drive app to use classes for each vehicle type. They have classes representing cars, trucks, and motorcycles.
Here’s what their class structure looks like right now:
Thanks to customer demand, management has asked that steering be added to all vehicle types. Mike, Got-A-Motor’s rookie developer, thinks he has this requirement covered.
But Marcy, the team’s experienced object-oriented developer, has some reservations about this approach.
Marcy is right; this is a maintenance nightmare waiting to happen. First, let’s figure out how to address the duplication. Then we’ll fix the steer
instance method for Motorcycle
objects.
Fortunately, like most object-oriented languages, Ruby includes the concept of inheritance, which allows classes to inherit methods from one another. If one class has some functionality, classes that inherit from it can get that functionality automatically.
Instead of repeating method definitions across many similar classes, inheritance lets you move the common methods to a single class. You can then specify that other classes inherit from this class. The class with the common methods is referred to as the superclass, and the classes that inherit those methods are known as subclasses.
If a superclass has instance methods, then its subclasses automatically inherit those methods. You can get access to all the methods you need from the superclass, without having to duplicate the methods’ code in each subclass.
Inheritance allows multiple subclasses to inherit methods from a single superclass.
Here’s how we might use inheritance to get rid of the repetition in the virtual test-drive app...
We see that the Car
, Truck
, and Motorcycle
classes have several instance methods and attributes in common.
Each one of these classes is a type of vehicle. So we can create a new class, which we’ll choose to call Vehicle
, and move the common methods and attributes there.
Then, we can specify that each of the other classes inherits from the Vehicle
class.
The Vehicle
class is called the superclass of the other three classes. Car
, Truck
, and Motorcycle
are called subclasses of Vehicle
.
The subclasses inherit all the methods and attributes of the superclass. In other words, if the superclass has some functionality, its subclasses automatically get that functionality. We can remove the duplicated methods from Car
, Truck
, and Motorcycle
, because they will automatically inherit them from the Vehicle
class. All of the classes will still have the same methods, but there’s only one copy of each method to maintain!
Note that in Ruby, subclasses technically do not inherit instance variables; they inherit the attribute accessor methods that create those variables. We’ll talk about this subtle distinction in a few pages.
To eliminate the repeated methods and attributes in our Car
, Truck
, and Motorcycle
classes, Marcy has created this design. It moves the shared methods and attributes to a Vehicle
superclass. Car
, Truck
, and Motorcycle
are all subclasses of Vehicle
, and they inherit all of Vehicle
’s methods.
There’s actually no special syntax to define a superclass in Ruby; it’s just an ordinary class. (Most object-oriented languages are like this.)
The syntax for subclasses isn’t much more complicated. A subclass definition looks just like an ordinary class definition, except that you specify the superclass it will inherit from.
Ruby uses a less-than ( <
) symbol because the subclass is a subset of the superclass. (All cars are vehicles, but not all vehicles are cars.) You can think of the subclass as being lesser than the superclass.
So here’s all we have to write in order to specify that Car
, Truck
, and Motorcycle
are subclasses of Vehicle
:
class Car < Vehicle end class Truck < Vehicle end class Motorcycle < Vehicle end
As soon as you define them as subclasses, Car
, Truck
, and Motorcycle
inherit all the attributes and instance methods of Vehicle
. Even though the subclasses don’t contain any code of their own, any instances we create will have access to all of the superclass’s functionality!
Our Car
, Truck
, and Motorcycle
classes have all the same functionality they used to, without all the duplicated code. Using inheritance will save us a lot of maintenance headaches!
As it stands, there’s no difference between our Truck
class and the Car
or Motorcycle
classes. But what good is a truck, if not for hauling cargo? Got-A-Motor wants to add a load_bed
method for Truck
instances, as well as a cargo
attribute to access the bed contents.
It won’t do to add cargo
and load_bed
to the Vehicle
class, though. The Truck
class would inherit them, yes, but so would Car
and Motorcycle
. Cars and motorcycles don’t have cargo beds!
So instead, we can define a cargo
attribute and a load_bed
method directly on the Truck
class.
class Truck < Vehicle attr_accessor :cargo def load_bed(contents) puts "Securing #{contents} in the truck bed." @cargo = contents end end
If we were to draw the diagram of Vehicle
and its subclasses again now, it would look like this:
With these code changes in place, we can create a new Truck
instance, then load and access its cargo.
A subclass that defines its own methods doesn’t lose the ones it inherits from its superclass, though. Truck
will still have all the attributes and methods it inherits from
If we redrew our diagram with the inherited attributes and methods included, it would look like this:
So in addition to the cargo
attribute and load_bed
method, our Truck
instance can also access all the old inherited attributes and methods it used to.
So, a subclass inherits instance methods from its superclass. Does it also inherit instance variables?
Surprisingly, the answer is no! Bear with us; we need to take a two-page detour to explain...
It’s easy to form the impression that instance variables defined in a superclass get inherited by subclasses, but that’s not how it works in Ruby. Let’s take another look at our class diagram, focusing on the attribute accessor methods of the Vehicle
and Car
classes...
You might assume that Car
would inherit @odometer
and @gas_used
instance variables from Vehicle
. Well, let’s test that... All Ruby objects have a method called instance_variables
that we can call to see what instance variables are defined for that object. Let’s try creating a new Car
object and see what instance variables it has.
There’s no output because car
doesn’t have any instance variables right now! The object won’t get any instance variables until we call some instance methods on it, at which point the method will create the variables on the object. Let’s call the odometer
and gas_used
attribute writer methods, then check the list of instance variables again.
So the Car
class didn’t inherit the @odometer
and @gas_used
instance variables...it inherited the odometer=
and gas_used=
instance methods, and the methods created the instance variables!
In many other object-oriented languages, instance variables are declared on the class, so Ruby differs in this respect. It’s a subtle distinction, but one worth knowing about...
So why’s it important to know that instance variables belong to the object and not the class? As long as you follow the convention and ensure your instance variable names match your accessor method names, you won’t have to worry about it. But if you deviate from that convention, look out! You may find that a subclass can interfere with its superclass’s functionality by overwriting its instance variables.
Say we had a superclass that breaks from convention and uses the @storage
instance variable to hold the value for its name=
and name
accessor methods. Then suppose that a subclass uses the same variable name, @storage
, to hold the value for its salary=
and salary
accessor methods.
When we try to actually use the Employee
subclass, we’ll find that any time we assign to the salary
attribute, we overwrite the name
attribute, because both are using the same instance variable.
Make sure you always use sensible variable names that match your attribute accessor names. That simple practice should be enough to keep you out of trouble!
Marcy, the team’s experienced object-oriented developer, has rewritten our Car
, Truck
, and Motorcycle
classes as subclasses of Vehicle
. They don’t need any methods or attributes of their own—they inherit everything from the superclass! But Mike points out an issue with this design...
If the superclass’s behavior isn’t what you need in the subclass, inheritance gives you another mechanism to help: method overriding. When you override one or more methods in a subclass, you replace the inherited methods from the superclass with methods specific to the subclass.
class Motorcycle < Vehicle def steer puts "Turn front wheel." end end
Now, if we call steer
on a Motorcycle
instance, we’ll get the overriding method—that is, we’ll get the version of steer
defined within the Motorcycle
class, not the version from Vehicle
.
If we call any other methods on a Motorcycle
instance, though, we’ll get the inherited method.
How does this work?
If Ruby sees that the requested method is defined on a subclass, it will call that method and stop there.
But if the method’s not found, Ruby will look for it on the superclass, then the superclass’s superclass, and so on, up the chain.
Everything seems to be working again! When changes are needed, they can be made in the Vehicle
class, and they’ll propagate to the subclasses automatically, meaning everyone gets the benefit of updates sooner. If a subclass needs specialized behavior, it can simply override the method it inherited from the superclass.
Nice work cleaning up Got-A-Motor’s classes! Next, we’ll take another look at the Fuzzy Friends code. They still have a lot of redundant methods in their application’s classes. We’ll see if inheritance and method overriding can help them out.
Remember the Fuzzy Friends virtual storybook application from last chapter? We did a lot of excellent work on the Dog
class. We added name
and age
attribute accessor methods (with validation), and updated the talk
, move
, and report_age
methods to use the @name
and @age
instance variables.
Here’s a recap of the code we have so far:
The Bird
and Cat
classes have been completely left behind, however, even though they need almost identical functionality.
Let’s use this new concept of inheritance to create a design that will bring all our classes up to date at once (and keep them updated in the future).
We’ve added lots of new functionality to our Dog
class, and now we want it in the Cat
and Bird
classes as well...
We want all the classes to have name
and age
attributes, as well as talk
, move
, and report_age
methods. Let’s move all of these attributes and methods up to a new class, which we’ll call Animal
.
Then, we’ll declare that Dog
, Bird
, and Cat
are subclasses of Animal
. All three subclasses will inherit all the attributes and instance methods from their superclass. We’ll instantly be caught up!
Here’s code for the Animal
superclass, with all the old methods from Dog
moved into it...
And here are the other classes, rewritten as subclasses of Animal
.
With our Dog
, Bird
, and Cat
classes rewritten as subclasses of Animal
, they don’t need any methods or attributes of their own—they inherit everything from the superclass!
Looks good, except for one problem...our Cat
instance is barking.
The subclasses inherited this method from Animal
:
def talk puts "#{@name} says Bark!" end
That’s appropriate behavior for a Dog
, but not so much for a Cat
or a Bird
.
This code will override the talk
method that was inherited from Animal
:
Now, when you call talk
on Cat
or Bird
instances, you’ll get the overridden methods.
Next up, Fuzzy Friends wants to add armadillos to their interactive storybook. (That’s right, they want the little anteater-like critters that can roll into an armored ball. We’re not sure why.) We can simply add Armadillo
as a subclass of Animal
.
There’s a catch, though: before the armadillos can run anywhere, they have to unroll. The move
method will have to be overridden to reflect this fact.
This works, but it’s unfortunate that we have to replicate the code from the move
method of the Animal
class.
What if we could override the move
method with new code, and still harness the code from the superclass? Ruby has a mechanism to do just that...
When you use the super
keyword within a method, it makes a call to a method of the same name on the superclass.
If we make a call to the overriding method on the subclass, we’ll see that the super
keyword makes a call to the overridden method on the superclass:
The super
keyword works like an ordinary method call in almost every respect.
For example, the superclass method’s return value becomes the value of the super
expression:
Another way in which using the super
keyword is like a regular method call: you can pass it arguments, and those arguments will be passed to the superclass’s method.
But here’s a way that super
differs from a regular method call: if you leave the arguments off, the superclass method will automatically be called with the same arguments that were passed to the subclass method.
Now let’s use our new understanding of super
to eliminate a little duplicated code from the move
method in our Armadillo
class.
Here’s the method we’re inheriting from the Animal
superclass:
And here’s the overridden version in the Armadillo
subclass:
We can replace the duplicated code in the subclass’s move
method with a call to super
, and rely on the superclass’s move
method to provide that functionality.
Here, we explicitly pass on the destination
parameter for Animal
’s move
method to use:
But we could instead leave off the arguments to super
, and allow the destination
parameter to be forwarded to the superclass’s move
method automatically:
Either way, the code still works great!
Your mastery of class inheritance has wrung the repetition out of your code like water from a sponge. And your coworkers will thank you—less code means fewer bugs! Great job!
Let’s make one more improvement to our Dog
class, before we declare it finished. Right now, if we pass a Dog
instance to the print
or puts
methods, the output isn’t too useful:
We can tell that they’re Dog
objects, but beyond that it’s very hard to tell one Dog
from another. It would be far nicer if we got output like this:
When you pass an object to the puts
method, Ruby calls the to_s
instance method on it to convert it to a string for printing. We can call to_s
explicitly, and get the same result:
Now, here’s a question: Where did that to_s
instance method come from?
Indeed, where did most of these instance methods on Dog
objects come from? If you call the method named methods
on a Dog
instance, only the first few instance methods will look familiar...
Instance methods named clone
, hash
, inspect
... We didn’t define them ourselves; they’re not on the Dog
class. They weren’t inherited from the Animal
superclass, either.
But—and here’s the part you may find surprising—they were inherited from somewhere.
Where could our Dog
instances have inherited all these instance methods from? We don’t define them in the Animal
superclass. And we didn’t specify a superclass for Animal
...
Ruby classes have a superclass
method that you can call to get their superclass. The result of using it on Dog
isn’t surprising:
But what happens if we call superclass
on Animal
?
Whoa! Where did that come from?
When you define a new class, Ruby implicitly sets a class called Object
as its superclass (unless you specify a superclass yourself).
So writing this:
class Animal ... end
...is equivalent to writing this:
class Animal < Object ... end
If you don’t explicitly specify a superclass for a class you define, Ruby implicitly sets a class named Object
as the superclass.
Even if you do specify a superclass for your class, that superclass probably inherits from Object
. That means almost every Ruby object, directly or indirectly, has Object
as a superclass!
Ruby does this because the Object
class defines dozens of useful methods that almost all Ruby objects need. This includes a lot of the methods that we’ve been calling on objects so far:
The to_s
method converts an object to a string for printing.
The inspect
method converts an object to a debug string.
The class
method tells you which class an object is an instance of.
The methods
method tells you what instance methods an object has.
The instance_variables
method gives you a list of an object’s instance variables.
And there are many others. The methods inherited from the Object
class are fundamental to the way Ruby works with objects.
We hope you’ve found this little tangent informative, but it doesn’t help us with our original problem: our Dog
objects are still printing in a gibberish format.
Or does it?
Ruby objects inherit dozens of essential methods from the Object class.
We specified that the superclass of the Dog
class is the Animal
class. And we learned that because we didn’t specify a superclass for Animal
, Ruby automatically set the Object
class as its superclass.
That means that Animal
instances inherit a to_s
method from Object
. Dog
instances, in turn, inherit to_s
from Animal
. When we pass a Dog
object to puts
or print
, its to_s
method is called, to convert it to a string.
Do you see where we’re headed? If the to_s
method is the source of the gibberish strings being printed for Dog
instances, and to_s
is an inherited method, all we have to do is override to_s
on the Dog
class!
Are you ready? Let’s try it.
It works! No more # < Dog:0x007fb2b50c4468>
. This is actually readable!
One more tweak: the to_s
method is already called when printing objects, so we can leave that off:
This new output format will make debugging the virtual storybook much easier. And you’ve gained a key insight into how Ruby objects work: inheritance plays a vital role!
What would happen if you created a new Dog
instance, but called move
on it before you set its name
attribute? (Try it if you want; the result won’t look very good.) In the next chapter, we’ll look at the initialize
method, which can help prevent that sort of mishap.
3.141.38.121