Classes & OOP: Static vs. Dynamic Polymorphism

Roger PoonBy Roger Poon

JS++ Designer and Project Lead

Static polymorphism is polymorphism that occurs at compile time, and dynamic polymorphism is polymorphism that occurs at runtime (during application execution).

An aspect of static polymorphism is early binding. In early binding, the specific method to call is resolved at compile time. (JS++ also supports late binding via virtual functions which we will cover later.) Early binding is faster because there is no runtime overhead. Early binding is the default behavior and most common for JS++. If you want late binding, you have to explicitly specify it. Again, we'll cover late binding in a later section.

In our code, we specified the 'overwrite' modifier for our 'Cat' and 'Dog' class render() methods. This enabled method hiding, but it specifies early binding. Here's the code from Cat.jspp:

    overwrite void render() {
        $element.attr("title", _name);
        super.render();
    }
    

Later, when we changed the type of our cats to 'Animal' in main.jspp, the type of our cats was changed from 'Cat' to 'Animal'. You might say, "Yes, but we instantiated our cat objects using the 'Cat' class nonetheless." While this is true, it also means our cat variables can now accept data of any type satisfying the 'Animal' constraint (including 'Animal' itself if we didn't specify a protected constructor on 'Animal'). For example, this now becomes completely acceptable code:

    import System;
    import Animals;

    Animal cat1 = new Cat("Kitty");
    if (Math.random(1, 10) > 3) {
        cat1 = new Dog("Fido");
    }
    cat1.render();
    

Ignore the 'System' import, but it imports the Math.random() function. It's used for illustrating the example. I also purposefully kept the name of our variable as 'cat1' to illustrate the point. 'cat1' has type 'Animal'. Thus, all objects of type 'Animal' can be assigned to 'cat1' (including objects of type 'Dog').

If you actually compile, run the code above, and refresh sufficiently, you'll notice that your "cat" will sometimes be rendered as a dog.

As you can observe, when we have a type 'Animal', the data can be 'Cat' in our program or it can also randomly become a 'Dog' based on runtime random number generation. The random numbers are not generated at compile time. The random numbers are generated during application execution (runtime). Thus, from the compiler's perspective, if we are going to resolve the 'render' method, we are going to resolve it to the 'render' method of the user-specified type, 'Animal', because it is the most correct. Recall that in subtyping, all Cats and Dogs are Animals, but not all Animals are Cats and Dogs.

Thus, this should hopefully help you understand early binding and static/compile-time polymorphism.

Aside: Data Types as Specifications

Data types are also specifications. We are specifying what constitutes a correct program. For instance, you wouldn't specify a 'subtract' function to accept two parameters of type 'string'. In our case, if we wanted a cat, we should have specified the type 'Cat' rather than generalizing to 'Animal'. While the ability to express algorithms in more general terms is desirable, it may not always be correct.

Technically, the program is still type-correct. In both cases ('Cat' and 'Dog'), we have an object of type 'Animal'. It just happens that our 'Animal' was named 'cat1' so one potential fix for the code above may be to rename the variable to 'animal' or something similar to express our intent.

The other potential fix, if we want 'cat1' to always be a cat, is to restrict the data type to 'Cat' instead of 'Animal'. If you do this, you'll get a compile error because 'Dog' is not a subtype of 'Cat':

[ ERROR ] JSPPE5000: Cannot convert `Animals.Dog' to `Animals.Cat' at line 6 char 8 at main.jspp

In runtime polymorphism, the type is determined at runtime. For example, we can use the 'instanceof' operator to check the runtime data type. Change your main.jspp file and observe the result:

    import System;
    import Animals;

    external $;

    Animal animal = new Cat("Kitty");
    if (Math.random(1, 10) > 3) {
        animal = new Dog("Fido");
    }

    if (animal instanceof Cat) {
        $("#content").text("We have a CAT.");
    }
    else {
        $("#content").text("We have a DOG.");
    }
    

Keep refreshing and you should see that sometimes we have a 'Cat' instance and sometimes we have a 'Dog' instance. However, the types (and resulting messages) are determined at runtime — not compile time.