Classes & OOP: Abstract Classes and Methods

Roger PoonBy Roger Poon

JS++ Designer and Project Lead

We have explored virtual methods and 'overwrite' (early binding) and 'override' (late binding) which allow us to define base implementations for a method and more specific implementations of the method in subclasses. However, what do we do if there is no relevant base implementation that makes sense? Consider a 'talk' method. While a 'Dog' will "woof" and a 'Cat' will "meow, " what will the 'Animal' base class do? Abstract classes and methods allow us to solve this problem.

When a class is declared with the 'abstract' modifier, it cannot be instantiated. Furthermore, it allows the class to have "abstract methods." Abstract methods are methods that don't define an implementation, and the implementation is left to the derived classes. The derived classes must implement all abstract methods when inheriting from an abstract class; otherwise, we'll get a compile error.

Let's start by making our 'Animal' class in Animal.jspp abstract and declaring an abstract 'talk' method:

    external $;

    module Animals
    {
        abstract class Animal
        {
            protected var $element;
            private static unsigned int count = 0;

            protected Animal(string iconClassName) {
                string elementHTML = makeElementHTML(iconClassName);
                $element = $(elementHTML);

                Animal.count++;
            }

            public static unsigned int getCount() {
                return Animal.count;
            }

            public virtual void render() {
                $("#content").append($element);
            }

            public abstract void talk();

            private string makeElementHTML(string iconClassName) {
                string result = '<div class="animal">';
                result += '<i class="icofont ' + iconClassName + '"></i>';
                result += "</div>";
                return result;
            }
        }
    }
    

If we try to compile right now, we'll get compiler errors demanding that the subclasses of 'Animal' implement the 'talk' abstract method.

Let's start with Cat.jspp:

    import Externals.DOM;

    external $;

    module Animals
    {
        class Cat : Animal
        {
            string _name;

            Cat(string name) {
                super("icofont-animal-cat");
                _name = name;
            }

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

            final void talk() {
                alert("Meow!");
            }
        }
    }
    

We import 'Externals.DOM' because this module provides all the 'external' declarations for the DOM (Document Object Model) API for browsers. This allows us to use the 'alert' function which will pop up a message box with what we want the cat to say ("Meow!"). We could have also just declared 'alert' as 'external'. Notice that we used the 'final' modifier to override 'talk', but we could have just as well used 'override'.

We're going to implement Dog.jspp similarly, but dogs make a different sound:

    import Externals.DOM;

    external $;

    module Animals
    {
        class Dog : Animal
        {
            string _name;

            Dog(string name) {
                super("icofont-animal-dog");
                _name = name;
            }

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

            final void talk() {
                alert("Woof!");
            }
        }
    }
    

Now let's implement Panda.jspp. Pandas can bark, so let's make it bark:

    import Externals.DOM;

    external $;

    module Animals
    {
        class Panda : Animal
        {
            Panda() {
                super("icofont-animal-panda");
            }

            final void talk() {
                alert("Bark!");
            }
        }
    }
    

One last class to implement: 'Rhino'. Let's assume rhinos don't make a sound. It's perfectly fine to just override the abstract method and have it do nothing:

    external $;

    module Animals
    {
        class Rhino : Animal
        {
            Rhino() {
                super("icofont-animal-rhino");
            }

            final void talk() {
            }
        }
    }
    

Notice that we didn't import 'Externals.DOM' either. We didn't need it. Our rhino doesn't talk so we don't need the 'alert' function for showing a message box.

At this point, your project should be able to compile without errors. However, our animals don't actually talk yet. We need some kind of event to trigger the animal talking. For example, we might want an animal to talk when we click on it. To achieve this, we need event handlers, and this is the subject of the next section.