Inheritance
This post assumes you understand how to create a class and what a class is.
What's inheritance?
With the exception of BasicObject ( The uppermost class in Ruby ) all classes inherit from a superclass. To understand this better lets create two initial classes:
1 class Animal
2 attr_accessor :name, :gender
3
4 def initialize(name, gender)
5 @name = name
6 @gender = gender
7 end
8
9 def living
10 puts "I breathe"
11 end
12 end
13
14 class Human < Animal
15
16 def can_drive
17 puts "I can probably drive a car at some point in my life"
18 end
19
20 end
At this stage we are just trying to visiualize the inheritance heirarchy and what that does for these newly created classes. Our Human class inherits from our Animal class ( denoted by the less than symbol < ) also stated as, our Human class is a subclass of Animal, and Animal is the superclass of Human. Yet another way, the Human class is the child of the parent class, Animal. What this means is any Human objects have access to any methods, as well as its own, defined in the Animal class.
So quick example of this:
# new instance of the Human class created
jason = Human.new('Jason', 'Male')
# => <Human:0x000001020133f0 @name="Jason", @gender="Male">
jason.living # => "I breathe"
jason.can_drive # => "I can probably drive a car at some point in my life"
Clearly we can see that whilst the living method was defined in the parent class our Human object still has access to it, due to inheritance.
So hopefully that’s pretty straight forward at this stage but if we dig a little deeper we can very quickly begin to understand how some of the things we write in ruby just seem to happen. For example if I call puts on my Human object look what happens:
puts jason # => <Human:0x000001020133f0>
In this example I’ve called the to_s method on my Human class jason using puts, but if I look in the classes I created I didn’t define a to_s method so where did my object find that method and what was it doing outside of Human and Animal?
It was looking up the inheritance chain just as the Human class did when I called jason.living. First of all my object looked for the living method in Human and when it didn’t find it there it looked in its parent class Animal and found it. So what would have happened if it wasn’t there? Well it would have looked in Animals parent class (superclass) which happens to be Object and if it hadn’t have found the method there it would have looked in the parent class of Object which is the end of the line for this line of inheritance as it reaches the empty class BasicObject which would have returned nil and thrown a noMethod error.
But when we called puts on jason it must have found the to_s method somewhere and indeed it did, inside of Object. What this essentially means is that when we create a new class we automatically inherit all of the methods up the chain. To find out just how many are inherited do:
Human.instance_methods.count # => 61
We can go a step further and see what methods our class has access to by doing Human.instance_methods
So there we have a great example of inheritance at work.
How do we deal with state and inheritance?
The state of our class instances are defined with instance variables. In our above example it was fairly simple as our child class didn’t have any of its own instance variables and so just inherited its parent classes initialize method which contained @name and @gender. Therefor when we created a new instance of Human we only needed to pass in the parent classes two instance variables like this:
jason = Human.new('Jason', 'Male')
That’s fine but what if the Human class has it’s own instance variables, how would we handle that? Lets look at the above code again with a slight variation:
1 class Animal
2 attr_accessor :name, :gender
3
4 def initialize(name, gender)
5 @name = name
6 @gender = gender
7 end
8
9 def living
10 puts "I breathe"
11 end
12 end
13
14 class Human < Animal
15 attr_accessor :name, :gender, :job_title
16
17 def initialize(gender, name, job_title)
18 super(name, gender)
19 @job_title = job_title
20 end
21
22 def can_drive
23 puts "I can probably drive a car at some point in my life"
24 end
25
26 end
Now you’ll notice on line 17 a new initialize method with 3 arguments:
def initialize(gender, name, job_title)
super(name, gender)
@job_title = job_title
end
There are a couple of important factors here. Thie above shows us that if your class has instance variables and inherits from a class that also has instance variables then we have to take the arguments the parent class needs for its own instance variables as well as our own. To do this we must do two things. The first is we need to pass the args into the initialize method and the second is we need to handle them using the ‘super’ keyword. You’ll see in the above code we pass the two args for the parent class into ‘super’ like this super(name, gender) which leads us to the second important factor which is the order we pass these args into super is very important. The order must match the parents intialize methods order so:
# if the order in the parent class is ...
def initialize(name, gender)
@name = name
@gender = gender
end
# then the order in super in the child class must be ..
super(name, gender)
If it is not in this order your object might look something like this:
# => #<Human:0x000001010d4588 @name="Female", @gender="Vic", @job_title="keeper">
When use inheritance?
When it seems right to seperate elements of an object into different classes for example, when different objects share common states and behaviours it is probably a good time to think about a superclass and subclass the more intracate details
You should think about using inheritance when much of the behaviour and state of an object is generic and when your object needs state or behaviour that is neccessary for the object concerned but not all objects of the class. For example, a Human object and a Snake object could both have a @name, @gender and be able to breathe, all inherited from our Human class but a Human might have a @job_title and a @pay_scale and be able drive a car all of which do not apply to a Snake object and so should’nt apply to Snake. Inheritance here might work well as Human and Snake are both a specialization of Animal.
How else can we deal with shared behaviour?
Another way to deal with shared behaviour is through mixins. For example lets say that we also had a Dog, Cat and Monkey class. Now we have a Human, Snake, Monkey, Dog & Cat and we want to give all but the Snake the ability to jump. We could use inheritance but we would need to write the same jump method into all the classes that needed it (NOT DRY) and we couldn’t write it in our Animal class becase then Snake would be able to jump and I don’t think Snakes can jump. The answer is to write the method once and place it inside a module and then when we needed it we can include it in our class. Like this:
module Jumpable
def jumps
puts 'I can jump!'
end
end
class Human
include Jumpable
end
class Snake
end
class Monkey
include Jumpable
end
# ...
In the above example our Human and Monkey objects can jump but our Snake objects cannot.
When to use inheritance and when to use modules?
In the examples in this post it was perfect for both inheritance and mixins. Inheritance was perfect for sharing the really general shared states and behaviours like @name, @gender and the breathe method and using the mixin approach was perfect for distributing the jump behaviour for some, but not all, of our animals. I can also think about modules on a wider scale than I can inheritance because I might be able to reuse the Jumpable module throughout my program for instance I might have a ToyFrog class in the zoo giftshop that could use my Jumpable module. Inheritance on the other hand I think of in more of a ‘is a specialist of’ kind of way - Human, Snake, Monkey, Dog & Cat are all specialists of the Animal class - whilst they need the basics from Animal they have to have their own additional states and behaviours.