20
INHERITANCE IN RUBY Savannah Worth As you start working on larger and more complicated projects, it’s likely you’ll end up with a lot of classes. Organizing all this code becomes complicated quickly; however, Ruby has ways to give our classes structure and define relationships between them. Inheritance allows us to create this organization.

Inheritance (annotated)

Embed Size (px)

DESCRIPTION

An intro to inheritance in the Ruby programming language, now with annotations!

Citation preview

Page 1: Inheritance (annotated)

INHERITANCE IN RUBYSavannah Worth

As you start working on larger and more complicated projects, it’s likely you’ll end up with a lot of classes. Organizing all this code becomes complicated quickly; however, Ruby has ways to give our classes structure and define relationships between them. Inheritance allows us to create this organization. 

Page 2: Inheritance (annotated)

Uses of inheritance:

Help organize our classes

Reduces repetition in our code

Help create specific classes that inherit general behavior

Using inheritance, a relationship is established between two classes in which one class can reference the other. This reference allows our class to gain access to the methods of the class it is inheriting from. This has several important benefits for us as programmers. !First, it makes it easier for us to follow what’s going on, and keep track of which class does what.

Second, it reduces repetition in our code. By letting one class use a method from another class, we eliminate the need to copy the same method in both classes. This is huge if we need to go back and change that method later. Cutting down repetition makes it easier to reuse our code, and helps

reduce bugs in our code.

Finally, inheritance allows the creation of specialized classes that share a common set of methods. When we talk about things such as creating controllers in Rails, this benefit of inheritance is key. 

Page 3: Inheritance (annotated)

What is a class in Ruby?

!So that’s what inheritance can do for us. Before we see some code, let’s talk about classes a little. Making a class is easy—give it a name, fill it full of methods and variables, pretty straightforward. But to really get what’s going on with inheritance, we have to understand the idea of references—these labels we

put on enclosed bits of code so Ruby can find them later. !Imagine that when we make a class, it is a box that is filled with code. And that box has a duct-tape label on the side that has our class’s name written on it, so that when Ruby goes looking for some code within that class, it can just read labels of classes. It doesn't need to go into the box until it finds the label

it’s looking for. We can also refer to this label as a class pointer.

Page 4: Inheritance (annotated)

An enclosed set of behaviors (methods) and data (instance variables)

A class in ruby contains a method table

We can think of a class as an enclosed set of behaviors and data. We’ll be focused on the behaviors (methods) for our discussion of inheritance. The methods within our class have their own names/labels, which work much the same. Ruby uses the name to find a method within a class, so that it doesn’t have to

look at the code within the method until it finds the one it’s looking for. We can think of a class as having a method table—a list of the methods contained within the class. Inheritance gives one class a way to reference another class’s method table. 

Page 5: Inheritance (annotated)

Planet !!!!!!

Methods: !

noun !

Class Pointer

Method Table

I’ll be using the abstraction on the right to represent classes. There’s no particular reason I’ve chose to represent them this way except that it works well to draw attention to the references contained within the class. There’s no magic happening here with these references—they’re just words. When you call the

’noun’ method on an instance of our Planet class, all Ruby does is look for a method called ’noun’ within the Planet class. These references are the only links between the code we call and the classes and methods we’ve already written, hence why they’re so important!

Page 6: Inheritance (annotated)

Inheritance is a way for a class to gain access to the methods of another class

Page 7: Inheritance (annotated)

Terminology:

Parent-child

Superclass-subclass

Parent

Child Classes

Planet

IceGiant GasGiant RockyPlanet

So, inheritance. This diagram illustrates the kind of relationship it would be effective to model with inheritance. Inheritance works best in ‘is-a’ type relationships. The examples I’m going to be using throughout this post are Planet, IceGiant, and GasGiant. IceGiants and GasGiants are types of Planets—in other

words, an IceGiant is a type of Planet, and therefore should do planet-y type things, like orbit and be big and such. Just like you’d have a far easier time explaining what an ice giant is to someone who is already knows what a planet is, you would have a much easier time defining the IceGiant class if you had a

Planet class to build off of. Inheritance to the rescue! !Also, key terms: the inheritance relationship is between a parent class and a child class, where the child class inherits from the parent class. You’ll also hear the term superclass and subclass, where the subclass inherits from the superclass. Both are used, and mean the same thing.

Page 8: Inheritance (annotated)

EXAMPLE!

Code time! Here’s a simple version of what these classes could look like: !class Planet

def noun

puts "Planet!"

end

end !# This defines a class, GasGiant, which inherits from the Planet class

class GasGiant < Planet

def material

puts "Gas!"

end

end !# This defines a class, IceGiant, which inherits from the Planet class

class IceGiant < Planet

def material

puts "Ice!"

end

end !This defines three classes, Planet, IceGiant, and GasGiant, and makes IceGiant and GasGiant inherit from the Planet class. All of the inheritance magic happens on the very first line of the class definition. Following the name of the class is the < symbol followed by the name of the class that will become the

parent, which is Planet in both these cases. !That's it! Now our IceGiant class and our GasGiant class will have access to the 'noun' method from our Planet class. Running the following code produces the commented outputs: !neptune = IceGiant.new

Page 9: Inheritance (annotated)

IceGiant Planet

neptune.noun

neptune = IceGiant.new

Methods: !

material

Methods: !

noun

<earth = Planet.new

earth.material

Planet

X

So let’s step through the method lookup and see what’s happening here. All method calls in Ruby work the same—they look for a method with the name we called within the class of the object we called that method on. So calling the noun method on the neptune object causes Ruby to go and look in the

IceGiant class for a method called  noun. All it’s doing is looking for it by name. IceGiant class doesn’t have it’s own noun method, so if we had not set up this inheritance relationship, we would get a no method error at this point.  !However, we do have an inheritance relationship! So this is going to give Ruby another place to look. The IceGiant class is actually going to contain a little label of its own that says ‘Planet’. Ruby can follow that label back to the Planet class. It’ll then rummage through Planet class’s method table, and there it

will actually find a ‘noun' method. It then runs that code, and that’s it! Our method call has been fulfilled, and we get the string ‘Planet’.  !It’s important at this point to note that the IceGiant class never gets the ‘noun' method—it just gains a reference to it. In addition, the inheritance relationship only goes one direction. If we made an instance of the Planet class, and attempted to call the material method on it, we would get a no method error.

Method calls only move up the method chain—never back down. 

Page 10: Inheritance (annotated)

What happens if we make a method in the

child class with the same name as a

method in the parent class?

Method calls only are looking for something with the right method name in the class definition, so what happens if there is a method in the child class that has the same name as a method in the parent class? If we alter the IceGiant class like so:

Page 11: Inheritance (annotated)

EXAMPLE!

class IceGiant < Planet

def material

puts "Ice!"

end ! def noun

puts "Ice Giant!"

end

end !so that now it has its own 'noun' method while still inheriting from the Planet class, and then we run !neptune = IceGiant.new !neptune.noun #=> ‘Ice Giant!’ !you can see that now our method call only gives us the code from the IceGiant class’s method! Our parent class’s noun method was never touched. 

Page 12: Inheritance (annotated)

IceGiantMethods:

material noun

PlanetMethods:

!

noun

neptune.noun

neptune = IceGiant.new

<

Planet

This makes sense when we consider how method calls work. We want a method called 'noun', and Ruby knows  the IceGiant class is the first place to look. There's never any need for it to go look in the Planet class's code.

Page 13: Inheritance (annotated)

If we want to call the parent class’s method of the same name, we can use the super keyword.

Making a method in the child class with the same name as one in the parent class will block access to the parent class's method. Sometimes this is good; other times we'll want to run some code from the parent class’s method, and also include some specialized code in a child class's method. I've seen this

happen a lot with 'initialize'. When this is the case, Ruby provides a way to get access to the parent class's method from the child class's. It's the 'super' keyword.

Page 14: Inheritance (annotated)

EXAMPLE!

We edit our IceGiant class to look like this: !class IceGiant < Planet

def material

puts "Ice!"

end ! def noun

super

puts "Ice Giant!"

end

end !So now the Ice Giant class has the super keyword in its 'noun' method. Now if we call the noun method on our Ice Giant object… !neptune = IceGiant.new !neptune.noun

#=> 'Planet!'

#=> 'Ice Giant!’ ! And now it returns two strings! First ‘Planet!' and then ‘Ice Giant!'. You'll notice this mirrors the structure of the method itself. On the first line is the super keyword, and the Planet string is returned first. Then the second line is puts ‘Ice Giant!'. If we moved the super keyword to the last line of the method, the

‘Ice Giant!' string would be returned first and the ‘Planet!' string second.

Page 15: Inheritance (annotated)

IceGiantMethods:

!material

noun (super)

PlanetMethods:

!

noun

neptune.noun

neptune = IceGiant.new

<

Planet

What super is doing is pretty simple. When our program encounters it within a method, it knows to go up and look in the parent class for another method that shares the current method's name. So we're in ‘noun', Ruby hits 'super', it goes up to the Planet class and looks for another method named 'noun'. It's

all about the names--that's the only connection between our two methods. Once it finishes running this parent method, it drops back into the child method, and finishes running the code there. So when 'super' came first, the ‘Planet!' string was returned and then the ‘Ice Giant!' string. There's more to 'super'

than I can cover now, and it's worth some research. However, moving on to some more general notes about inheritance.

Page 16: Inheritance (annotated)

Ruby has single inheritance*—a class can only have one parent.

CelestialBody

Planet

IceGiant

But you can inherit from a class that’s

inheriting from

another!

Parent of Planet

Parent of IceGiant and Child of

CelestialBody

Child of Planet

*You can model multiple inheritance using modules

Ruby has single inheritance (you can model multiple inheritance using modules, but that's a whole other topic). Basically, that means you can only put one class name following the <. However, you can inherit from a class that is inheriting from another class, so that the class in the middle is a parent to one

class and a child to another. These roles only exist in relation to each other.

Page 17: Inheritance (annotated)

CelestialBody

Planet

IceGiant

neptune = IceGiant.newmethods

orbit

neptune.orbit

So say our CelestialBody class has this orbit method. Since neptune is a specific IceGiant, and an IceGiant is a specific type of Planet, and a Planet is a specific type of CelestialBody, it makes sense that neptune can orbit! So we call: !neptune.orbit !And this method call will move right up the inheritance chain, looking for an orbit method. First IceGiant, then Planet, before it finally finds it in CelestialBody! Then it runs the code, no problem. A method call will just keep looking up the chain until it runs out of classes, at which point we get a no method

error.

Page 18: Inheritance (annotated)

When and why do we use inheritance?

Useful for general classes that branch off into more specific classes

Child classes get the higher functionality and also can add their own.

Reduces repetition and makes changing our code easier

Page 19: Inheritance (annotated)

Inheritance is not always the right tool

Good for “is-a” type relationships

Not as good for “has-a” type relationships.

You'll hear sometimes that using inheritance is a bad idea. Inheritance is a tool. Sometimes it's the right one and sometimes not. It’s great for ‘is-a’ type relationships. Like here, IceGiant is a type of Planet, and therefore should do Planet-y type things. On the other hand, an IceGiant has a Moon, but if we had

a Moon class we likely wouldn’t want it to be inheriting from the IceGiant class since that is a ‘has-a’ type relationship rather than an ‘is-a’ type relationship. We don’t really want our Moon to do IceGiant things. In such ‘has-a’ type cases, there are alternatives to inheritance we can use, specifically modules and

composition.

Page 20: Inheritance (annotated)

@SavannahDWorth

Anyway, that’s all I got! Please hit me up with any comments/questions on twitter; my handle is @savannahdworth. !Thanks! !Savannah Worth