Classes & OOP: Access Modifiers

Roger PoonBy Roger Poon

JS++ Designer and Project Lead

Access modifiers allow us to change the "visibility" and "access privileges" of a class (or module) member. These are best understood by example.

JS++ has three access modifiers: private, protected, and public.

Examples & Illustrations

A private member is the least permissive. If a member is declared as 'private' it can only be accessed from the class or module it was declared. Here's an example:

        class Animal
        {
            private string name;

            string getName() {
                return name; // OK
            }
        }
        class Dog : Animal
        {
            string getName() {
                return name; // Error
            }
        }
        Animal animal = new Animal();
        animal.name;      // ERROR
        animal.getName(); // OK
        

A protected member can be accessed from any subclass or submodule. Here's an example:

        class Animal
        {
            protected string name;

            string getName() {
                return name; // OK
            }
        }
        class Dog : Animal
        {
            string getName() {
                return name; // OK
            }
        }
        Animal animal = new Animal();
        animal.name;      // ERROR
        animal.getName(); // OK
        

Finally, there is the 'public' access modifier. The 'public' access modifier is the least permissive. A member declared as 'public' has no restrictions on access and can even be accessed from outside the class (provided it is being accessed from a class instance). Here's an example:

        class Animal
        {
            public string name;

            string getName() {
                return name; // OK
            }
        }
        class Dog : Animal
        {
            string getName() {
                return name; // OK
            }
        }
        Animal animal = new Animal();
        animal.name;      // OK
        animal.getName(); // OK
        

Encapsulation

Access modifiers enable encapsulation. Encapsulation is one of the pillars of object-oriented programming (as we discussed in chapter 12) and refers to the bundling of data (fields) and the methods that operate on that data (e.g. methods, getters/setters, etc). In simpler terms: hide your data by making fields private and only enable access to them via public/protected methods, getters, or setters.

The JS++ default access rules enable encapsulation. In JS++, fields have a default access modifier of 'private'. All other class members have a default access modifier of 'public'. In other words, JS++ access rules are "member-sensitive, " whereas you usually needed to manually specify the access modifier in languages like Java and C# in order to achieve encapsulation, which can result in verbose code.

Why do we need encapsulation? Think back to our getter and setter examples where we had to define getter and setter methods to read and modify our cat's 'name' field. Hypothetically, suppose our requirements change and we wanted to prefix all of our cat names with "Kitty". With encapsulation, we would only need to change our setter method. If, instead, we made our field 'public' and the name had to be manipulated directly through its instances, we would have to manually add the prefix to every direct manipulation of the 'name' field by an instance. As projects grow in complexity, this would not be desirable.

Method Hiding with 'overwrite'

Now that we have a firm understanding of access modifiers and encapsulation, let's return to our project. We need our 'Cat' class to render() differently from what the 'Animal' base class provides. The first step is to edit our 'Animal' base class to make the $element field 'protected' so that the field can be accessed by our derived classes (like 'Cat'):

        external $;

        module Animals
        {
            class Animal
            {
                protected var $element = $(
                    """
                    <div class="animal">
                        <i class="icofont icofont-animal-cat"></i>
                    </div>
                    """
                );

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

Next, let's restore the render() method to 'Cat':

        external $;

        module Animals
        {
            class Cat : Animal
            {
                string _name;

                Cat(string name) {
                    _name = name;
                }

                void render() {
                    $element.attr("title", _name);
                    $("#content").append($element);
                }
            }
        }
        

If you try to compile right now, you will get a compile error. The error itself should be quite descriptive:

[ ERROR ] JSPPE0252: `void Animals.Cat.render()' conflicts with `void Animals.Animal.render()'. Either create a method with a different name or use the 'overwrite' modifier

In this case, our derived class ('Cat') tried to define a method named 'render', but the base class ('Animal') already has a method named 'render'. Thus, we have a conflict. JS++ also suggests fixes for us: A) create a method with a different name, or B) use the 'overwrite' modifier.

Conceptually, both methods describe one concept: rendering to a web page. Thus, we might not want two different names to describe the same concept. Instead, we want to tell the JS++ compiler that this was intentional by using the 'overwrite' modifier:

        external $;

        module Animals
        {
            class Cat : Animal
            {
                string _name;

                Cat(string name) {
                    _name = name;
                }

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

In other object-oriented languages, this is known as "method hiding" or "method shadowing." The reason JS++ does this is to prevent potential mistakes and typos (especially for more complex classes). If we had two different concepts, such as 'Cat' rendering in memory while 'Animal' renders to the web page, we should not have the same method names in such a case.

Compile your code now. It should succeed. Open the web page, and you should now be able to hover your mouse over the cats again to see their names.

The 'super' Keyword

At this stage, we still have code duplication. Here's a look at the 'Animal' render() method:

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

And here's our 'Cat' render() method:

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

Do you notice the duplication? What if we wanted to render to a different HTML element besides the one with the ID 'content' later? We would have to change the rendering code in all relevant classes!

Our 'Cat' class "extends" the concept of an 'Animal'. Likewise, our Cat's render() method "extends" the Animal's render() method by adding the HTML 'title' attribute so we can mouse over and see the name. However, besides that, our rendering logic is the same: add the element to HTML element with ID 'content'. We can do better. Let's "re-use" the rendering code from our 'Animal' class in our 'Cat' class:

        external $;

        module Animals
        {
            class Cat : Animal
            {
                string _name;

                Cat(string name) {
                    _name = name;
                }

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

Compile, run, and observe the results. Now, no matter how our rendering logic changes, it'll be applied to all relevant classes. The key is the 'super' keyword. The 'super' keyword refers to the superclass of the current class. In this case, we use it to access the 'render' method of the 'Animal' class. Without 'super', we'd be calling the 'render' method of the current class - resulting in infinite recursion! (For example, using 'this' instead of 'super' will allow you to refer to the 'render' method of the 'Cat' class... but it'll result in infinite recursion.)

Abstraction

Thus far, we've learned about private, protected, and public fields and methods, but what about constructors? Open main.jspp and add the following code:

        import Animals;

        Cat cat1 = new Cat("Kitty");
        cat1.render();
        Cat cat2 = new Cat("Kat");
        cat2.render();
        Animal animal = new Animal();
        animal.render();
        

Compile and run.

Uh-oh! We've got three cats rendered to the page. At least when you hover over the last cat, it doesn't show a name. However, an 'Animal' is not a 'Cat' (but a 'Cat' is an 'Animal'). The reason we have three cat icons is because we have this in our Animal.jspp:

        protected var $element = $(
            """
            <div class="animal">
                <i class="icofont icofont-animal-cat"></i>
            </div>
            """
        );
        

In other words, when our $element field is initialized, it is always initialized to a value that gives us a cat icon ("icofont icofont-animal-cat"). Instead, we may want to define a constructor on 'Animal' to parameterize this initialization. Let's change Animal.jspp so that this field is initialized in the constructor:

        external $;

        module Animals
        {
            class Animal
            {
                protected var $element;

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

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

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

I added access modifiers on all class members to make the code clearer. I also separated the construction of the HTML text into a separate function for clarity. Get into the habit of practicing the Single Responsibility Principle: all classes do one thing, all functions/methods do one thing. In the above code, our constructor does one thing: initialize the fields; our render() method does one thing: render to the web page; and, finally, our 'makeElementHTML' method does one thing: generate the HTML for our element. This leads to clean code, and JS++ was designed with clean code in mind so try to benefit from the design.

Another neat trick you may have noticed is to use ' (single quotes) to wrap HTML strings as shown in the code above. This is to avoid escaping the " (double quotes) used to surround the HTML attributes in our 'makeElementHTML' method.

You may have noticed that all the new access modifiers are different: protected constructor, public render(), and private makeElementHTML. Let's break this down from the most restrictive (private) to the least restrictive (public).

The reason 'makeElementHTML' is private is because it is an implementation detail. The only use of 'makeElementHTML' is inside our 'Animal' class. The 'Cat' class cannot access the method, and main.jspp cannot access the method (via instantiation). The 'Cat' class will never need to call 'makeElementHTML' — instead, the 'Cat' class inherits from the 'Animal' class. Via inheritance, the 'Cat' class will call the 'Animal' constructor. (We'll get to this shortly as the code currently cannot compile, but these concepts are more important to understand first.) As a consequence, the 'Cat' class will call 'makeElementHTML' via the 'Animal' class constructor, but it has no access to the method and is not able to call it directly. In this way, 'makeElementHTML' is an implementation detail of the 'Animal' class and is not exposed to any other portion of our code. This hiding of details that are irrelevant to other classes and code is known as "abstraction" in object-oriented programming.

As we mentioned in chapter 12, abstraction is the other fundamental pillar of object-oriented programming (OOP). For example, imagine a car. When you press the gas pedal on a car, you don't need to know how the specifics of the internal combustion engine works. The complexities of the inner workings are presented to you through a simplified interface: the gas pedal. Via abstraction, we are making complex systems simple, a desirable property of OOP.

After the private 'makeElementHTML' method, the next code with access privileges is the 'protected' constructor. Once again, the 'protected' access modifier is less restrictive than 'private' but not as permissive as 'public' (which has no access restrictions beyond scoping).

Specifically, what does it mean to make a constructor 'protected'? Recall that the 'protected' access modifier allows all members within the class to access but also includes all derived classes. Recall as well that instantiation of a class executes the code specified in the constructor. Logically, we're able to conclude that a protected constructor will mean that a class cannot be instantiated outside of specific contexts.

What are these specific contexts? The obvious case is that we cannot instantiate 'Animal' from main.jspp. If you try it right now, you'll get a compile error. However, since 'protected' can only be accessed from within the class itself and all derived classes, the intention of the protected constructor in our code is to restrict the class to inheritance only. Recall that the 'Cat' class cannot call the private 'makeElementHTML' directly; this method gets executed via the 'Animal' constructor during inheritance. During inheritance, the constructor gets executed just like in instantiation.

If you were to make the constructor 'private', you would essentially prevent instantiation and inheritance for the class. (Side Note: This is how the JS++ Standard Library 'System.Math' class is implemented.) Remember: the default access rule for everything except fields is 'public'. In other words, if we left the access modifier for our constructor unspecified, it would have defaulted to 'public'.

The 'super' keyword we used previously to access methods of the superclass refers to an instance of the superclass created during instantiation. When we instantiate 'Cat', we also instantiate 'Animal'. All relevant constructors will be executed up the chain, starting from the bottom of the chain. In our case, we execute the 'Cat' constructor first when 'Cat' is instantiated, and we move up the inheritance chain and execute the 'Animal' class constructor next. (JS++ uses a "unified type system" where 'System.Object' is the root of all internal types so the constructor for this class will also be called — but only if it's determined to be necessary and not a candidate for "dead code elimination" — but this is outside the scope of this chapter and will be discussed in the Standard Library chapters.)

Knowing that constructors get called during inheritance, we can now address the remaining problem in our code: you will notice that the code currently does not compile. The reason is because we've stopped using the implicit default constructor for the 'Animal' class when we defined a custom constructor.

Our 'Animal' constructor takes one parameter:

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

We need to change the 'Cat' constructor code so that we specify how the superclass constructor should be called. We can do this once again via the 'super' keyword. The 'Animal' class wants to know the icon name of the type of animal we want to render. If you don't remember the name of the icon, I've included it here in the 'super' call for your convenience:

        external $;

        module Animals
        {
            class Cat : Animal
            {
                string _name;

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

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

A function call on the 'super' keyword will execute the relevant constructor of the superclass. The 'super' call must always be the first statement because, semantically, the superclass's constructor will be executed before the constructor code of its derived classes.

Finally, we need to modify main.jspp to remove the instantiation of the 'Animal' class. Remember that since we made the 'Animal' constructor 'protected', we will be unable to instantiate 'Animal' from main.jspp anyway:

        import Animals;

        Cat cat1 = new Cat("Kitty");
        cat1.render();
        Cat cat2 = new Cat("Kat");
        cat2.render();
        

At this point, you can compile and the project should compile successfully. Once again, we should have the two cats:

Adding More Animals

Finally, we can add more animals.

Dog.jspp:

        external $;

        module Animals
        {
            class Dog : Animal
            {
                string _name;

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

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

Dog.jspp is very much like Cat.jspp because a dog is also a domesticated animal that needs a name.

Panda.jspp:

        external $;

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

Unlike Cat.jspp and Dog.jspp, Panda.jspp is significantly simpler. All the 'Panda' class does is inherit from 'Animal' and specify the icon to render. It has no name, and its render() method is exactly the same as Animal's since it doesn't have to add an HTML 'title' attribute on mouse over to display a name.

Rhino.jspp:

        external $;

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

Just like Panda.jspp, Rhino.jspp is also a very simple class. It just inherits from 'Animal' and has no need to set or render a name.

Finally, modify main.jspp to instantiate the new animals:

        import Animals;

        Cat cat1 = new Cat("Kitty");
        cat1.render();
        Cat cat2 = new Cat("Kat");
        cat2.render();
        Dog dog = new Dog("Fido");
        dog.render();
        Panda panda = new Panda();
        panda.render();
        Rhino rhino = new Rhino();
        rhino.render();
        

Compile the entire project like so:

$ js++ src/ -o build/app.jspp.js

Once again, on all platforms (Windows, Mac, and Linux), we are operating from the command-line so the compilation instructions should be the same for everyone. Also, it's completely unnecessary to specify the "compile order." It does not matter that 'Cat' depends on 'Animal' so, consequently, 'Animal.jspp' should be processed before 'Cat.jspp'. JS++ automatically resolves the compile order for you even for the most complex projects (e.g. with cyclic imports and complex dependencies). Just specify the input directories and let JS++ recursively find the input files and figure out the compilation order.

Open index.html in your web browser. The result should look like this:

Verify that your two cats have names, your dog has a name, but the panda and rhino should not have names when you hover your mouse over.

If everything works: congratulations! At this point, you may have noticed we can change our inheritance hierarchy as follows:

        Animal
        |_ DomesticatedAnimal
            |_ Cat
            |_ Dog
        |_ WildAnimal
            |_ Panda
            |_ Rhino
        

However, this is left as an exercise to the reader.

We've now covered three of the four fundamental concepts of OOP that we discussed in the chapter introducing classes and object-oriented programming: abstraction, encapsulation, and inheritance. The last fundamental pillar of OOP is polymorphism, which we'll cover in the next section.