Having recently graduated from the Flatiron School, I am working my way through programming-related reading, beginning with Sandi Metz’s “Practical Object-Oriented Design in Ruby”. Click here for more posts about POODR.
What’s inheritance?
The idea of inheritance may seem complicated but as with all complexity, there’s a simplifying abstraction. Inheritance is, at its core, a mechanism for automatic message delegation. It defines a forwarding path for not-understood messages. It creates relationships such that, if one object cannot respond to a received message, it delegates that message to another. You don’t have to write code to explicitly delegate the message, instead you define an inheritance relationship between two objects and the forwarding happens automatically.
That’s it, that’s all there is to inheritance. Not really. There’s a lot to inheritance, and it’s one of the things that makes Ruby the language that it is, in all its object-oriented glory.
In this post, I’ll speed through a couple key lessons about classical inheritance. Some of it was old news, some of it brand new.
Classical?
Referring specifically to “classical” inheritance is simply to refer to “class”-based inheritance. This distinguishes it from other inheritance techniques (like modules in Ruby, prototypical inheritance in JavaScript, or any number of others). Classical inheritance deals with subclasses and superclasses (from which subclasses inherit).
Single vs. Multiple Inheritance
Multiple inheritance can get complicated (how do we know from which ancestor a descendant will inherit?). Metz explains that “Many object-oriented languages [including Ruby] sidestep these complications by providing single inheritance, whereby a subclass is allowed only one parent superclass. Ruby does this; it has single inheritance. A superclass may have many subclasses, but each subclass is permitted only one superclass” (112).
Inherent Inheritance (inheritance and nil?
)
Whether you’ve implemented a class hierarchy or not, if you’ve used Ruby then you’ve used inheritance. An example: nil?
. Ruby contains two implementations of that method, one in NilClass
and the other in Object
. When nil?
is called on an instance of NilClass
, it returns true
. On everything else, because everything elses is a subclass of Object
, the nil?
message travels up the superclass hierarchy to Object
, which will then return false
. See the image below for a depiction of this.
Creating a Hierarchy has Costs (to duplicate or inherit?)
Any decision to implement inheritance should take into consideration the costs. Metz presents an example of a MountainBike
and a RoadBike
class, both of which could inherit from the Bicycle
superclass. But she asks, “Even though you now have a requirement for two kinds of bikes, this still may not be the right moment to commit to inheritance” and continues “A decision to proceed with the hierarchy accepts the risk that you may not yet have enough information to identify the correct abstraction. Your choice about whether to wait or to proceed hinges on how soon you expect a third bike to appear versus how much you expect the duplication to cost” (118-119).
“Push-everything-down-and-then-pull-some-things-up strategy”
That’s a technical term, I think. It’s a direct quote, anyway. Metz describes that, in implementing inheritance, it is important to push certain things down to the subclass, then pull them back up to the superclass to be inherited (and vice versa), even though it means moving code around multiple times. Her reasoning? Every programmer needs to ask themself: “What will happen when I’m wrong?”. Metz argues that “Every decision you make includes two costs: one to implement it and another to change it when you discover that you were wrong. Taking both costs into account when choosing among alternatives motivates you to make conservative choices that minimize the cost of change” (123).
Super
keywords
“Sending super
in any method passes that message up the superclass chain” (115). This is essential for inheritance. Adding super
to a method in a subclass will inherit the code of that same method of the subclass’ parent. If you’re confused, Stack Overflow’s got you covered.
Metz’s code
I’ve copied Metz’s final example of inheritance using RoadBike
, MountainBike
, and Bicycle
classes. As you can see, many of the methods (initialize
, spares
, different default
s, etc.) are shared by the superclass, but some are unique to the subclasses.
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
|
This is a finished example, but Metz likes to go through what she calls “antipatterns”—common patterns that appear beneficial but are actually detrimental—to demonstrate a concept, so make sure to read the full text.