Warning
This is being rewritten based on the Python version, there may be errors.
In the fairy tales about heroes defeating evil villains there's always a dark forest of some kind. It could be a cave, a forest, another planet, just some place that everyone knows the hero shouldn't go. Of course, shortly after the villain is introduced you find out, yes, the hero has to go to that stupid forest to kill the bad guy. It seems the hero just keeps getting into situations that require him to risk his life in this evil forest.
You rarely read fairy tales about the heroes who are smart enough to just avoid the whole situation entirely. You never hear a hero say, "Wait a minute, if I leave to make my fortunes on the high seas leaving Buttercup behind I could die and then she'd have to marry some ugly prince named Humperdink. Humperdink! I think I'll stay here and start a Farm Boy For Rent business." If he did that there'd be no fire swamp, dying, reanimation, sword fights, giants, or any kind of story really. Because of this, the forest in these stories seems to exist like a black hole that drags the hero in no matter what they do.
In object oriented programming, Inheritance is the evil forest. Experienced programmers know to avoid this evil because they know that deep inside the Dark Forest Inheritance is the Evil Queen Meta-Programming. She likes to eat software and programmers with her massive complexity teeth, chewing on the flesh of the fallen. But, the forest is so powerful and so tempting that nearly every programmer has to go into it, and try to make it out alive with the Evil Queen's head before they can call themselves real programmers. You just can't resist the Inheritance Forest's pull, so you go in. After the adventure you learn to just stay out of that stupid forest and bring and army if you are ever forced to go in again.
This is basically a funny way to say that I'm going to teach you something you should avoid called Inheritance. Programmers who are currently in the forest battling the Queen will probably tell you that you have to go in. They say this because they need your help since what they've created is probably too much for them to handle. But, you should always remember this:
Most of the uses of inheritance can be simplified or replaced with composition, and meta-programming should be used sparingly.
Inheritance is used to indicate that one class will get most or all of its features from a parent class. This happens implicitly whenever you write class Foo < Bar which says "Make a class Foo that inherits from Bar." When you do this, the language makes any action that you do on instances of Foo also work as if they were done to an instance of Bar. Doing this lets you put common functionality in the Bar class, then specialize that functionality in the Foo class as needed.
When you are doing this kind of specialization, there's three ways that the parent and child classes can interact:
I will now demonstrate each of these in order and show you code for them.
First I will show you the implicit actions that happen when you define a function in the parent, but not in the child.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Parent
def implicit()
puts "PARENT implicit()"
end
end
class Child < Parent
end
dad = Parent.new()
son = Child.new()
dad.implicit()
son.implicit()
|
This creates a class named Child but says that there's nothing new to define in it. Instead it will inherit all of its behavior from Parent. When you run this code you get the following:
PARENT implicit()
PARENT implicit()
Notice how even though I'm calling son.implicit() on line 16, and even though Child does not have a implicit function defined, it still works and it calls the one defined in Parent. This shows you that, if you put functions in a base class (i.e. Parent) then all subclasses (i.e. Child) will automatically get those features. Very handy for repetitive code you need in many classes.
The problem with implicitly having functions called is sometimes you want the child to behave differently. In this case you want to override the function in the child, effectively replacing the functionality. To do this just define a function with the same name in Child. Here's an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Parent
def override()
puts "PARENT override()"
end
end
class Child < Parent
def override()
puts "CHILD override()"
end
end
dad = Parent.new()
son = Child.new()
dad.override()
son.override()
|
In this example example I have a function named override in both classes, so let's see what happens when you run it.
PARENT override()
CHILD override()
As you can see, when line 14 runs, it runs the Parent.override function because that variabe (dad) is a Parent. But, when line 15 runs it prints out the Child.override messages because son is an instance of Child and child overrides that function by defining it's own version.
Take a break right now and try playing with these two concepts before continuing.
The third way to use inheritance is a special case of overriding where you want to alter the behavior before or after you the parent class's version runs. You first override the function just like in the last example, but then you use a Ruby built-in function named super to get the Parent version to call. Here's the example of doing that so you can make sense of this description:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Parent
def altered()
puts "PARENT altered()"
end
end
class Child < Parent
def altered()
puts "CHILD, BEFORE PARENT altered()"
super()
puts "CHILD, AFTER PARENT altered()"
end
end
dad = Parent.new()
son = Child.new()
dad.altered()
son.altered()
|
The important lines here are 9-11, where in the child I do the following when son.altered() is called:
If you then run this you should see this:
PARENT altered()
CHILD, BEFORE PARENT altered()
PARENT altered()
CHILD, AFTER PARENT altered()
To demonstrate all of these, I have a final version that shows each kind of interaction from inheritance in one file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class Parent
def override()
puts "PARENT override()"
end
def implicit()
puts "PARENT implicit()"
end
def altered()
puts "PARENT altered()"
end
end
class Child < Parent
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE PARENT altered()"
super()
puts "CHILD, AFTER PARENT altered()"
end
end
dad = Parent.new()
son = Child.new()
dad.implicit()
son.implicit()
dad.override()
son.override()
dad.altered()
son.altered()
|
Go through each line of this code, and write a comment explaining what that line does and whether it's an override or not. Then, run it and see that you get what you expected:
PARENT implicit()
PARENT implicit()
PARENT override()
CHILD override()
PARENT altered()
CHILD, BEFORE PARENT altered()
PARENT altered()
CHILD, AFTER PARENT altered()
In the Child.altered() function I used a special function named super(). This isn't a function you define, but instead one Ruby gives you. This function figures out how to call the same function, but in a parent class. The reason you need a special function is that what is "the parent class" is a bit complex. It relies on Ruby knowing the full inheritance structure of the current class, and any possible modules you've added to it with mixins (which I'll show you soon). By using super() you don't need to worry about figuring this out and can just let Ruby do it.
You use super() by passing it the right number of arguments for the parent class's argument count. In the example above, neither version of altered() had arguments, so I just called super(). If the class Parent had arguments, then I'd add them to the super() call.
The most common use of super() is actually in initialize functions in base classes. This is usually the only place where you need to do some things in a child, then complete the initialization in the parent. Here's a quick example of doing that in the Child from these examples:
class Child < Parent
def initialize(self, stuff):
self.stuff = stuff
super()
end
end
This is pretty much the same as the Child.altered example above, except I'm setting some variables in the initialize before having the Parent initialize with its Parent.initialize.
Inheritance is useful, but another way to do the exact same thing is just to use other classes and modules, rather than rely on implicit inheritance. If you look at the three ways to exploit inheritance, two of the three involve writing new code to replace or alter functionality. This can easily be replicated by just calling functions on another class or from a module. Here's an example of doing this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class Other
def override()
puts "OTHER override()"
end
def implicit()
puts "OTHER implicit()"
end
def altered()
puts "OTHER altered()"
end
end
class Child
def initialize()
@other = Other.new()
end
def implicit()
@other.implicit()
end
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE OTHER altered()"
@other.altered()
puts "CHILD, AFTER OTHER altered()"
end
end
son = Child.new()
son.implicit()
son.override()
son.altered()
|
In this code I'm not using the name Parent, since there is not a parent-child is-a relationship. This is a has-a relationship, where Child has-a Other that it uses to get its work done. When I run this I get the following output:
OTHER implicit()
CHILD override()
CHILD, BEFORE OTHER altered()
OTHER altered()
CHILD, AFTER OTHER altered()
You can see that most of the code in Child and Other is the same to accomplish the same thing. The only difference is that I had to define a Child.implicit function to do that one action. I could then ask myself if I need this Other to be a class, and could I just make it into a module named Other. I totally could make this into a module instead:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | module Other
def Other.override()
puts "OTHER override()"
end
def Other.implicit()
puts "OTHER implicit()"
end
def Other.altered()
puts "OTHER altered()"
end
end
class Child
def implicit()
Other.implicit()
end
def override()
puts "CHILD override()"
end
def altered()
puts "CHILD, BEFORE OTHER altered()"
Other.altered()
puts "CHILD, AFTER OTHER altered()"
end
end
son = Child.new()
son.implicit()
son.override()
son.altered()
|
Nearly the exact same thing just using a module. The choice of which is better in the above code (class Other vs. module Other) depends on if you need to maintain state in each function call in Other. If each function can stand on its own, and they're mostly utilities then use a module. If however the set of functions make up a cohesive "thing" that keeps state then use a class.
The question of "inheritance vs. composition" comes down to an attempt to solve the problem of reusable code. You don't want to have duplicated code all over your code, since that's not clean and efficient. Inheritance solves this problem by creating a mechanism for you to have implied features in base classes. Composition solves this by giving you modules and the ability to simply call functions in other classes.
If both solutions solve the problem of reuse, then which one is appropriate in which situations? The answer is incredibly subjective, but I'll give you my three guidelines for when to do which:
However, do not be a slave to these rules. The thing to remember about object oriented programming is that it is entirely a social convention programmers have created to package and share code. Because it's a social convention, but one that's codified in Ruby, you may be forced to avoid these rules because of the people you work with. In that case, find out how they use things and then just adapt to the situation.
There is only one extra credit for this exercise because it is a big exercise. Go and read this https://github.com/styleguide/ruby and start trying to use it in your code. You'll notice that some of it is different from what you've been learning in this book, but now you should be able to understand their recommendations and use them in your own code. The rest of the code in this book may or may not follow these guidelines depending on if it makes the code more confusing. I suggest you also do this, as comprehension is more important than impressing everyone with you knowledge of esoteric style rules.