Most object-oriented programming languages, including Ruby, provide a subclassing mechanism that allows us to create new classes whose behavior is based on, but modified from, the behavior of an existing class. We’ll begin this discussion of subclassing with definitions of basic terminology. If you’ve programmed in Java, C++, or a similar language, you are probably already familiar with these terms.
When we define a class, we may specify that it
extends—or inherits
from—another class, known as the
superclass. If we define a class Ruby
that extends a class Gem
, we say that Ruby
is a subclass
of Gem
, and that
Gem
is the
superclass of Ruby
. If you do not specify a superclass when
you define a class, then your class implicitly extends Object
. A class may have any number of
subclasses, and every class has a single superclass except Object
, which has none.
The fact that classes may have multiple subclasses but only a
single superclass means that they can be arranged in a tree structure,
which we call the Ruby class hierarchy. The Object
class is the root of this hierarchy,
and every class inherits directly or indirectly from it. The descendants of a class are the
subclasses of the class plus the subclasses of the subclasses, and so on
recursively. The ancestors of a class are the superclass, plus the superclass of the
superclass, and so on up to Object
.
Figure 5-5 in Chapter 5
illustrates the portion of the Ruby class hierarchy that includes
Exception
and all of its descendants.
In that figure, you can see that the ancestors of EOFError
are IOError
, StandardError
, Exception
, and Object
.
The syntax for extending a class is simple. Just add a <
character and the name of the superclass
to your class
statement. For
example:
class Point3D < Point # Define class Point3D as a subclass of Point end
We’ll flesh out this three-dimensional Point
class in the subsections that follow,
showing how methods are inherited from the superclass, and how to
override or augment the inherited methods to define new behavior for the
subclass.
The Point3D
class we have defined is a trivial subclass of Point
. It declares itself an extension of Point
, but there is no class body, so it
adds nothing to that class. A Point3D
object is effectively the same thing
as a Point
object. One of the only
observable differences is in the value returned by the class
method:
p2 = Point.new(1,2) p3 = Point3D.new(1,2) print p2.to_s, p2.class # prints "(1,2)Point" print p3.to_s, p3.class # prints "(1,2)Point3D"
The value returned by the class
method is different, but what’s more
striking about this example is what is the same. Our Point3D
object has inherited the to_s
method defined by Point
. It has also inherited the initialize
method—this is what allows us to
create a Point3D
object with the
same new
call that we use to create
a Point
object.[*] There is another example of method inheritance in this
code: both Point
and Point3D
inherit the class
method from Object
.
When we define a new class, we add new behavior to it by defining new methods. Just as importantly, however, we can customize the inherited behavior of the class by redefining inherited methods.
For example, the Object
class
defines a to_s
method to convert an
object to a string in a very generic way:
o = Object.new puts o.to_s # Prints something like "#<Object:0xb7f7fce4>"
When we defined a to_s
method
in the Point
class, we were overriding the to_s
method inherited from Object
.
One of the important things to understand about object-oriented programming and subclassing is that when methods are invoked, they are looked up dynamically so that the appropriate definition or redefinition of the method is found. That is, method invocations are not bound statically at the time they are parsed, but rather, are looked up at the time they are executed. Here is an example to demonstrate this important point:
# Greet the World class WorldGreeter def greet # Display a greeting puts "#{greeting} #{who}" end def greeting # What greeting to use "Hello" end def who # Who to greet "World" end end # Greet the world in Spanish class SpanishWorldGreeter < WorldGreeter def greeting # Override the greeting "Hola" end end # We call a method defined in WorldGreeter, which calls the overridden # version of greeting in SpanishWorldGreeter, and prints "Hola World" SpanishWorldGreeter.new.greet
If you’ve done object-oriented programming before, the behavior
of this program is probably obvious and trivial to you. But if you’re
new to it, it may be profound. We call the greet
method inherited from WorldGreeter
. This greet
method calls the greeting
method. At the
time that greet
was defined, the
greeting
method returned “Hello”.
But we’ve subclassed WorldGreeter
,
and the object we’re calling greet
on has a new definition of greeting
. When we invoke greeting
, Ruby looks up the appropriate
definition of that method for the object it is being invoked on, and
we end up with a proper Spanish greeting rather than an English one.
This runtime lookup of the appropriate definition of a method is
called method name resolution, and is described
in detail in Method Lookup at the end of this
chapter.
Notice that it is also perfectly reasonable to define an abstract class that invokes certain undefined “abstract” methods, which are left for subclasses to define. The opposite of abstract is concrete. A class that extends an abstract class is concrete if it defines all of the abstract methods of its ancestors. For example:
# This class is abstract; it doesn't define greeting or who # No special syntax is required: any class that invokes methods that are # intended for a subclass to implement is abstract. class AbstractGreeter def greet puts "#{greeting} #{who}" end end # A concrete subclass class WorldGreeter < AbstractGreeter def greeting; "Hello"; end def who; "World"; end end WorldGreeter.new.greet # Displays "Hello World"
Private methods cannot be invoked from outside the class that defines them. But they are inherited by subclasses. This means that subclasses can invoke them and can override them.
Be careful when you subclass a class that you did not write yourself. Classes often use private methods as internal helper methods. They are not part of the public API of the class and are not intended to be visible. If you haven’t read the source code of the class, you won’t even know the names of the private methods it defines for its own use. If you happen to define a method (whatever its visibility) in your subclass that has the same name as a private method in the superclass, you will have inadvertently overridden the superclass’s internal utility method, and this will almost certainly cause unintended behavior.
The upshot is that, in Ruby, you should only subclass when you are familiar with the implementation of the superclass. If you only want to depend on the public API of a class and not on its implementation, then you should extend the functionality of the class by encapsulating and delegating to it, not by inheriting from it.
Sometimes when we override a method, we
don’t want to replace it altogether, we just want to augment its
behavior by adding some new code. In order to do this, we need a way
to invoke the overridden method from the overriding method. This is
known as chaining, and it is accomplished
with the keyword super
.
super
works like a special method invocation: it invokes a method
with the same name as the current one, in the superclass of the
current class. (Note that the superclass need not define that method
itself—it can inherit it from one of its ancestors.) You may specify
arguments for super
just as you
would for a normal method invocation. One common and important place
for method chaining is the initialize
method of a class. Here is how we
might write the initialize
method
of our Point3D
class:
class Point3D < Point def initialize(x,y,z) # Pass our first two arguments along to the superclass initialize method super(x,y) # And deal with the third argument ourself @z = z end end
If you use super
as a bare
keyword—with no arguments and no parentheses—then all of the arguments
that were passed to the current method are passed to the superclass
method. Note, however, that it’s the current values of the method
parameters that are passed to the superclass method. If the method has
modified the values in its parameter variables, then the modified
values are passed to the invocation of the superclass method.
As with normal method invocations, the parentheses around
super
arguments are optional.
Because a bare super
has special
meaning, however, you must explicitly use a pair of empty parentheses
if you want to pass zero arguments from a method that itself has one
or more arguments.
Class methods may be inherited and overridden just as instance methods
can be. If our Point
class defines
a class method sum
,
then our Point3D
subclass inherits
that method. That is, if Point3D
does not define its own class method named sum
, then the expression Point3D.sum
invokes the same method as the
expression Point.sum
.
As a stylistic matter, it is preferable to invoke class methods
through the class object on which they are defined. A code maintainer
seeing an expression Point3D.sum
would go looking for a definition of the sum
method in the Point3D
class, and he might have a hard time
finding it in the Point
class. When
invoking a class method with an explicit receiver, you should avoid
relying on inheritance—always invoke the class method through the
class that defines it.[*]
Within the body of a class method, you may invoke the other
class methods of the class without an explicit receiver—they are
invoked implicitly on self
, and the
value of self
in a class method is
the class on which it was invoked. It is here, inside the body of a
class method, that the inheritance of class methods is useful: it
allows you to implicitly invoke
a class method even when that class method is defined by a
superclass.
Finally, note that class methods can use super
just as instance methods can to invoke
the same-named method in the superclass.
Instance variables often appear to be inherited in Ruby. Consider this code, for example:
class Point3D < Point def initialize(x,y,z) super(x,y) @z = z end def to_s "(#@x, #@y, #@z)" # Variables @x and @y inherited? end end
The to_s
method in Point3D
references the @x
and @y
variables from the superclass Point
. This code works as you probably
expect it to:
Point3D.new(1,2,3).to_s # => "(1, 2, 3)"
Because this code behaves as expected, you may be tempted to say that these variables are inherited. That is not how Ruby works, though. All Ruby objects have a set of instance variables. These are not defined by the object’s class—they are simply created when a value is assigned to them. Because instance variables are not defined by a class, they are unrelated to subclassing and the inheritance mechanism.
In this code, Point3D
defines
an initialize
method that chains to the initialize
method of its superclass.
The chained method assigns values to the variables @x
and @y
, which makes those variables come into
existence for a particular instance of Point3D
.
Programmers coming from Java—or from other strongly typed languages in which a class defines a set of fields for its instances—may find that this takes some getting used to. Really, though, it is quite simple: Ruby’s instance variables are not inherited and have nothing to do with the inheritance mechanism. The reason that they sometimes appear to be inherited is that instance variables are created by the methods that first assign values to them, and those methods are often inherited or chained.
There is an important corollary. Because instance variables have nothing to do with inheritance, it follows that an instance variable used by a subclass cannot “shadow” an instance variable in the superclass. If a subclass uses an instance variable with the same name as a variable used by one of its ancestors, it will overwrite the value of its ancestor’s variable. This can be done intentionally, to alter the behavior of the ancestor, or it can be done inadvertently. In the latter case, it is almost certain to cause bugs. As with the inheritance of private methods described earlier, this is another reason why it is only safe to extend Ruby classes when you are familiar with (and in control of) the implementation of the superclass.
Finally, recall that class instance variables are simply
instance variables of the Class
object that represents a class. As such, they are not inherited.
Furthermore, the Point
and Point3D
objects (we’re talking about the
Class
objects themselves, not the
classes they represent) are both just instances of Class
. There is no relationship between
them, and no way that one could inherit variables from the
other.
Class variables are shared by a class and all of its subclasses.
If a class A
defines a variable
@@a
, then subclass B
can use that variable. Although this may
appear, superficially, to be inheritance, it is actually something
different.
The difference becomes clear when we think about setting the
value of a class variable. If a subclass assigns a value to a class
variable already in use by a superclass, it does not create its own
private copy of the class variable, but instead alters the value seen
by the superclass. It also alters the shared value seen by all other
subclasses of the superclass. Ruby 1.8 prints a warning about this if you run it with
-w
. Ruby 1.9 does not issue this
warning.
If a class uses class variables, then any subclass can alter the behavior of the class and all its descendants by changing the value of the shared class variable. This is a strong argument for the use of class instance variables instead of class variables.
The following code demonstrates the sharing of class variables.
It outputs 123
:
class A @@value = 1 # A class variable def A.value; @@value; end # An accessor method for it end print A.value # Display value of A's class variable class B < A; @@value = 2; end # Subclass alters shared class variable print A.value # Superclass sees altered value class C < A; @@value = 3; end # Another alters shared variable again print B.value # 1st subclass sees value from 2nd subclass
Constants are inherited and can be overridden, much like instance methods can. There is, however, a very important difference between the inheritance of methods and the inheritance of constants.
Our Point3D
class can use the
ORIGIN
constant defined by its Point
superclass, for example. Although the clearest style is to qualify
constants with their defining class, Point3D
could also refer to this constant
with an unqualified ORIGIN
or even
as Point3D::ORIGIN
.
Where inheritance of constants becomes interesting is when a
class like Point3D
redefines a constant. A
three-dimensional point class probably wants a constant named ORIGIN
to refer to a three-dimensional
point, so Point3D
is likely to
include a line like this:
ORIGIN = Point3D.new(0,0,0)
As you know, Ruby issues a warning when a constant is redefined.
In this case, however, this is a newly created constant. We now have
two constants Point::ORIGIN
and
Point3D::ORIGIN
.
The important difference between constants and methods is that
constants are looked up in the lexical scope of the place they are
used before they are looked up in the inheritance hierarchy (Constant Lookup has details). This means that if Point3D
inherits methods that use the
constant ORIGIN
, the behavior of
those inherited methods will not change when Point3D
defines its own version of ORIGIN
.
[*] If you’re a Java programmer, this may be surprising to you.
Java classes define special constructor methods for
initialization, and those methods are not inherited. In Ruby,
initialize
is an ordinary
method and is inherited like any other.
[*] The Class.new
method is
an exception—it is inherited by and invoked on just about every
new class we define.
3.144.41.148