Extending Class-Based Classes

© 2011, Martin Rinehart

The three hallmarks of object-oriented programming are, some say, encapsulation, inheritance and polymorphism. (See Wikipedia for a more complete set.)

To achieve the same benefits in JavaScript, we have to know what it means when someone from a class-based background (C++, Java, Ruby) speaks about "class inheritance." (There. I won't say "inher . . ." myself, but I'll quote others who say it.) Let's start with a class that everyone who puts stuff on the screen uses: Point. (No, not everyone uses the same names, but the idea is universal.) I'll use JavaScript literals, as that will be the language we all know.

var point = { x: 100, y: 200 };

Let's assume we're using screen coordinates (sorry, Descartes, 0,0 is not in the center). Our point is an inch plus from the left (about 3cm), and over two inches (7cm) down from the top. Now let's describe that with JavaScript's version of a classic object constructor and instance:

function Point( x, y ) {
    this.x = x;
    this.y = y;
}

var point = new Point( 100, 200 );

Class-Based Objects' Instance Data

In a strongly-typed, class-based language we would no doubt have declared our Point's coordinates to be integers. That will handle screens about a thousand times larger than today's biggest, so it ought to be an OK definition for the next year or two.

Our compiler would have taken point as the name for a memory address. It would have noted that address in a table so the next time your code referred to point it would remember where it had put it. It would have chosen an address that had space available for two ints. It would also have noted that the first int of every Point was named x and the second int was named y. So point.y is the second int following address point.

Now let's extend the Point class, creating a bigger class that can outline a rectangle. Let's use this notation: Rectangle extends Point. (That's Java. Java's designer/creator Bill Gosling apparently agreed with C++'s designer/creator Bjarne Stroustrup about the need for well-chosen words.) Tbe Point class could be thought of as representing the left-top corner of a rectangle. A second pair of coordinates could represent the right-bottom corner. In Java the keyword "super" (Gosling! You fell down on the job!) represents the constructor of the extended class, so we have:

// Java-style syntax:
function Rectangle extends Point(
        x, y, x2, y2 ) {
    super( x, y );
    this.x2 = x2;
    this.y2 = y2;
}

Now we've told our compiler that a Rectangle wants space for four ints. (Or we would have told our compiler this if we had declared the variables all to be integers. Let's pretend we did.) We've told it to reuse the names x and y for the first two and to use the names x2 and y2 for the second pair. Now, declaring an instance of the class finds an address with space available for four ints:

var rect = new Rectangle(
        100, 200, 300, 400 );

The rect instance var is stored at an address named rect. It has four integer values. rect.x2, for example, is the third integer. If it were an array of fixed-length entries, you might say that:

rect[ 2 ] === rect.x2

.

That's really all there is.

Class-Based Instance Data, Summarized

When one class extends another in Java (or does the same in another class-based OOP language) it just tells the compiler: "Leave enough space for the original object's instance data plus enough for the additional instance variables, please." And, it also tells the compiler that it can use the names it uses for the original class's variables when it refers to the same locations in the extending class. You also say, in whatever syntax, "Here is the space I need for more instance variables and names for these vars in this space, Mr. Compiler."

Now I'll repeat what I just said. Forgive me if you already totally get it. Extending a class adds to the list of instance variables. Each instance of the extending class has its own set of the original class's instance variables, for which it uses the original names, plus more, for which it provides new names.

Class-Based Object's Methods

In Java (and other class-based languages) the class defines the methods that all instances can use. These are called instance methods. Each instance method is stored in the class, available for use by any instance. In JavaScript you can add instance methods to the constructor's prototype, this way:

function Point( x, y ) {
    /* constructor code here */
}

Point.prototype.toString = function () {
    return 'Point{x=' + this.x +
	        ',y=' + this.y + '}';
}

The above toString() method will happily report on any instance, stating that it is a Point and showing its most important properties. (In this example, it shows all properties.)

Now let's extend Point to create a Rectangle class, as we did before. The first thing to note is that the extended class has an x and a y property. This means that the Point class's toString() could also be used on a Rectangle instance.

If you used our Point's toString() method to report on a Rectangle, it would report the values of the x and y coordinates. Assume, for the sake of argument, that you really didn't care how big your Rectangles were; you just care where they start. You would not have to write a new toString() method for your Rectangle class. You could just keep using the toString() method of the Point class. A class-based OOP practitioner would say that your Rectangle instances had "inherited" the toString() method from the Point class.

Now, for the sake of practicality, it is not likely that you will want to ignore the size of your Rectangle instances. Reporting just x and y will not be enough. We'll want to report x2 and y2 as well. You could write a brand-new toString() method for your Rectangle class to meet this need. You would no longer be "inheriting" the toString() method of the Point class. Your Rectangle class's toString() method would "override" the Point class's toString() method.

Summarizing Again!

And now you know what "inher . . ." means in class-based OOP. One class can "extend" another. That means the extending class will have all the original instance variables and the extending class will add a few more of its own. The extending class can continue to use the methods of the extended class, but they won't know about the new values the extending class defines. To get the new values, the extending class will have to provide its own methods. If the extending class creates a method (or methods) with the same name as a method (or methods) in the extending class, the new methods will "overrride" (be used in place of) the extended class's methods.

This business of extending classes is critically important in class-based OOP, as you can't just stick an extra property onto an object with C++ and its progeny. Your class definition must provide for any property you need or will need. In JavaScript (or Python, for another example) you can add any property you want to an object, whenever you see the need. Advance reservations are not required.

With the freedom of JavaScript, you can write lots of code and never worry about a class hierarchy. On the other hand, if a class hierarchy is a good fit for a particular application, knowing how to handle one is invaluable. That is where the next two articles in this series come in.


Feedback: MartinRinehart at gmail dot com

# # #