Chapter 3. Inheritance: Relying on Your Parents

image with no caption

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!

Copy, paste... Such a waste...

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:

image with no caption

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.

image with no caption

Mike’s code for the virtual test-drive classes

image with no caption

But Marcy, the team’s experienced object-oriented developer, has some reservations about this approach.

image with no caption

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.

Inheritance to the rescue!

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...

  1. We see that the Car, Truck, and Motorcycle classes have several instance methods and attributes in common.

    image with no caption
  2. 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.

    image with no caption
  3. Then, we can specify that each of the other classes inherits from the Vehicle class.

    image with no caption

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.

image with no caption

Defining a superclass (requires nothing special)

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.

image with no caption

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.)

image with no caption

Defining a subclass (is really easy)

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.

image with no caption

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!

image with no caption

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!

Adding methods to subclasses

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:

image with no caption

With these code changes in place, we can create a new Truck instance, then load and access its cargo.

image with no caption

Subclasses keep inherited methods alongside new ones

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

image with no caption

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.

image with no caption

So, a subclass inherits instance methods from its superclass. Does it also inherit instance variables?

image with no caption

Surprisingly, the answer is no! Bear with us; we need to take a two-page detour to explain...

Instance variables belong to the object, not the class!

image with no caption

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...

image with no caption

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.

image with no caption

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.

image with no caption

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.

image with no caption

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.

image with no caption

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.

image with no caption

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!

image with no caption

Overriding methods

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...

image with no caption
image with no caption
image with no caption

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.

image with no caption
image with no caption

If we call any other methods on a Motorcycle instance, though, we’ll get the inherited method.

image with no caption

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.

image with no caption

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.

Bringing our animal classes up to date with inheritance

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:

image with no caption

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).

Designing the animal class hierarchy

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.

image with no caption

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!

image with no caption

Code for the Animal class and its subclasses

Here’s code for the Animal superclass, with all the old methods from Dog moved into it...

image with no caption

And here are the other classes, rewritten as subclasses of Animal.

image with no caption

Overriding a method in the Animal subclasses

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!

image with no caption

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.

image with no caption

This code will override the talk method that was inherited from Animal:

image with no caption

Now, when you call talk on Cat or Bird instances, you’ll get the overridden methods.

image with no caption

We need to get at the overridden method!

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.

image with no caption
image with no caption

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...

The “super” keyword

When you use the super keyword within a method, it makes a call to a method of the same name on the superclass.

image with no caption

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:

image with no caption

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:

image with no caption

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.

image with no caption

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.

image with no caption

Watch it!

The calls super and super() are not the same.

By itself, super calls the overridden method with the same arguments the overriding method received. But super() calls the overridden method with no arguments, even if the overriding method did receive arguments.

A super-powered subclass

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:

image with no caption

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:

image with no caption

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!

image with no caption

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!

Difficulties displaying Dogs

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:

image with no caption

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:

image with no caption

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:

image with no caption

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...

image with no caption

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.

The Object class

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...

image with no caption

Ruby classes have a superclass method that you can call to get their superclass. The result of using it on Dog isn’t surprising:

image with no caption

But what happens if we call superclass on Animal?

image with no caption

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
image with no caption
image with no caption

Why everything inherits from the Object class

If you don’t explicitly specify a superclass for a class you define, Ruby implicitly sets a class named Object as the superclass.

image with no caption

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.

Overriding the inherited method

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!

image with no caption
image with no caption

Are you ready? Let’s try it.

image with no caption

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:

image with no caption

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!

Your Ruby Toolbox

That’s it for Chapter 3! You’ve added inheritance to your toolbox.

image with no caption

Up Next...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.38.121