Chapter 2. Class Hierarchies, Attributes, and Class Variables

image with no caption

We ended the previous chapter by creating two new classes: a Thing and a Treasure. Despite the fact that these two classes shared some features (notably both had a “name”), there was no connection between them.

These two classes are so trivial that this tiny bit of repetition doesn’t really matter much. However, when you start writing real programs of some complexity, your classes will frequently contain numerous variables and methods, and you really don’t want to keep coding the same things over and over again.

It makes sense to create a class hierarchy in which one class may be a “special type” of some other (ancestor) class, in which case it will automatically inherit the features of its ancestor. In our simple adventure game, for instance, a Treasure is a special type of Thing, so the Treasure class should inherit the features of the Thing class.

Note

In this book, I will often talk about descendant classes inheriting features from their ancestor classes. These terms deliberately suggest a kind a family relationship between “related” classes. Each class in Ruby has only one parent. It may, however, descend from a long and distinguished family tree with many generations of parents, grandparents, great-grandparents, and so on.

The behavior of Things in general will be coded in the Thing class. The Treasure class will automatically “inherit” all the features of the Thing class, so we won’t need to code them all over again; it will then add some additional features, specific to Treasures.

As a general rule, when creating a class hierarchy, the classes with the most generalized behavior are higher up the hierarchy than classes with more specialist behavior. So, a Thing class with just a name and a description would be the ancestor of a Treasure class that has a name, a description, and, additionally, a value; the Thing class might also be the ancestor of some other specialist class such as a Room that has a name, a description, and exits . . . and so on.

Let’s see how to create a descendant class in Ruby. Load the 1adventure.rb program. This starts simply enough with the definition of a Thing class, which has two instance variables, @name and @description.

1adventure.rb

class Thing
    def initialize( aName, aDescription )
      @name         = aName
      @description  = aDescription
    end

    def get_name
        return @name
    end

    def set_name( aName )
        @name = aName
    end

    def get_description
        return @description
    end

    def set_description( aDescription )
        @description = aDescription
    end
end

The @name and @description variables are assigned values in the initialize method when a new Thing object is created. Instance variables generally cannot (and should not) be directly accessed from the world outside the class itself, because of the principle of encapsulation (as explained in the previous chapter). To obtain the value of each variable, you need a get accessor method such as get_name; in order to assign a new value, you need a set accessor method such as set_name.

Superclasses and Subclasses

Now look at the Treasure class, which is also defined in the following program:

1adventure.rb

class Treasure < Thing
    def initialize( aName, aDescription, aValue )
        super( aName, aDescription )
        @value = aValue
    end

    def get_value
        return @value
    end

    def set_value( aValue )
        @value = aValue
    end
end

Notice how the Treasure class is declared:

class Treasure < Thing

The left angle bracket (<) indicates that Treasure is a subclass, or descendant, of Thing, and therefore it inherits the data (variables) and behavior (methods) from the Thing class. Since the methods get_name, set_name, get_description, and set_description already exist in the ancestor class (Thing), these methods don’t need to be recoded in the descendant class (Treasure).

The Treasure class has one additional piece of data, its value (@value), and I have written get and set accessors for this. When a new Treasure object is created, its initialize method is automatically called. A Treasure has three variables to initialize (@name, @description, and @value), so its initialize method takes three arguments. The first two arguments are passed, using the super keyword, to the initialize method of the superclass (Thing) so that the Thing class’s initialize method can deal with them:

super( aName, aDescription )

When used inside a method, the super keyword calls a method with the same name as the current method in the ancestor or superclass. If the super keyword is used on its own, without any arguments being specified, all the arguments sent to the current method are passed to the ancestor method. If, as in the present case, a specific list of arguments (here aName and aDescription) is supplied, then only these are passed to the method of the ancestor class.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.34.226