Member Keywords
***************

Keywords are defined within the context of the definition object (see Definition Object). In the sections that follow, let C denote a class that contains the definition object DFN, which in turn contains KEYWORDS within the declaration of method NAME, whose definition is denoted by VALUE.

The table below summarizes the available keywords accepted by KEYWORDS.

Keyword   Description
public
Places member NAME into the public API for C (see Access Modifiers).
protected
Places member NAME into the protected API for C
(see Access Modifiers).
private
Places member NAME into the private API for C
(see Access Modifiers).
static
Binds member NAME to class C rather than instance of
C.  Member data shared with each instance of type C.
See Static Members.
abstract
Declares member NAME and defers definition to subtype.
VALUE is interpreted as an argument list and must be of type
array. May only be used with methods. Member NAME must be part
of DFN of either an
Interface or AbstractClass. See Abstract Members.
const
Defines an immutable property NAME. May not be used with methods
or getters/setters. See Constants.
virtual
Declares that method NAME may be overridden by subtypes. Methods
without this keyword may not be overridden. May only be used with methods.
See Inheritance.
override
Overrides method NAME of supertype of C with VALUE.
May only override virtual methods. May only be used with methods.
See Inheritance.
proxy
Proxies calls to method NAME to the object stored in property
VALUE.

Table: Supported keywords

Not all keywords are supported by each member and some keywords conflict with each other. More information can be found in the appropriate sections as mentioned above in t:keywords.

Access Modifiers
================

Access modifiers, when provided in KEYWORDS, alter the interface into which the definition of member NAME is placed. There are three interfaces, or levels of visibility, that dictate the context from which a member may be accessed, listed here from the most permissive to the least:

public
     Accessible outside of C or any instance of C (e.g. foo.publicProp). Inherited by subtypes of C.

protected
     Not accessible outside of C or an instance of C (e.g. this.protectedProp within context of C). Inherited by subtypes of C.

private
     Not accessible outside of C or any instance of C. Not inherited by subtypes of C.

Keyword   Description
public

Places member NAME in public interface (accessible outside of C
or instance of C; accessible by subtypes).  Implied if no other access
modifier is provided.
protected

Places member NAME in protected interface (accessible only within
C or instance of C; accessible by subtypes).
private

Places member NAME in private interface (accessible only within
C or instance of C; not accessible by subtypes).

Table: Access modifiers

Access modifiers have the following properties:

  * Only one access modifier may appear in KEYWORDS for any given NAME.

  * If no access modifier is provided in KEYWORDS for any member NAME, member NAME is implicitly public.

Discussion
----------

One of the major hurdles ease.js aimed to address (indeed, one of the core reasons for its creation) was that of encapsulation. JavaScript's prototypal model provides limited means of encapsulating data. Since functions limit scope, they may be used to mimic private members; these are often referred to as privileged members. However, declaring classes in this manner tends be messy, which has the consequence of increasing maintenance costs and reducing the benefit of the implementation. ease.js aims to provide an elegant implementation that is both a pleasure to work with and able to support protected members.

By default, all members are public. This means that the members can be accessed and modified from within an instance as well as from outside of it. Subtypes (classes that inherit from it; see Inheritance) will inherit public members.  Public methods expose an API by which users may use your class. Public properties, however, should be less common in practice for a very important reason, which is explored throughout the remainder of this section.

Encapsulation
.............

Encapsulation is the act of hiding information within a class or instance.  Classes should be thought of black boxes; we want them to do their job, but we should not concern ourselves with how they do their job. Encapsulation takes a great deal of complexity out of an implementation and allows the developer to focus on accomplishing the task by focusing on the implementing in terms of the problem domain.

For example - consider a class named DOG which has a method walk().  To walk a dog, we simply call Dog().walk(). The walk() method could be doing anything. In the case of a real dog, perhaps it will send a message to the dog's brain, perform the necessary processing to determine how that command should be handled and communicate the result to the limbs. The limbs will communicate back the information they receive from their nerves, which will be processed by the brain to determine when they hit the ground, thereby triggering additional actions and the further movement of the other legs. This could be a terribly complicated implementation if we had to worry about how all of this was done.

In addition to the actual walking algorithm, we have the state of each of the legs - their current position, their velocity, the state of each of the muscles, etc. This state pertains only to the operations performed by the dog. Exposing this state to everyone wouldn't be terribly useful. Indeed, if this information was exposed, it would complicate the implementation. What if someone decided to alter this state in the middle of a walking operation? Or what if the developer implementing DOG relied on this state in order to determine when the leg reached a certain position, but later versions of DOG decided to alter the algorithm, thereby changing those properties?

By preventing these details from being exposed, we present the developer with a very simple interface (One would argue that this isn't necessary a good thing. What if additional flexibility was needed? DOG, in the sense of this example, can be thought of as a Facade (GoF). One could provide more flexibility by composing DOG of, say, LEG instances, a BRAIN, etc. However, encapsulation still remains a factor. Each of those components would encapsulate their own data.). Rather than the developer having to be concerned with moving each of the dog's legs, all they have to do is understand that the dog is being walked.

When developing your classes, the following best practices should be kept in mind:

  * When attempting to determine the best access modifier (see Access Modifiers) to use for a member, start with the least level of visibility (private) and work your way up if necessary.

  * If your member is not private, be sure that you can justify your choice.

       * If protected - why do subclasses need access to that data? Is there a better way to accomplish the same task without breaking encapsulation?

       * If public - is this member necessary to use the class externally? In the case of a method - does it make sense to be part of a public API? If a property - why is that data not encapsulated? Should you consider an accessor method?

Example
-------

Let's consider our DOG class in more detail. We will not go so far as to implement an entire nervous system in our example. Instead, let's think of our DOG similar to a wind-up toy:

Class( 'Dog',
{
    'private _legs': {},

    'private _body': {},

    // ...

    'public walk': function()
    {
        this.stand();
        this._moveFrontLeg( 0 );
        this._moveBackLeg( 1 );
        this._moveFrontLeg( 1 );
        this._moveBackLeg( 0 );
    },

    'protected stand': function()
    {
        if ( this.isSitting() )
        {
            // ...
        }
    },

    'public rollOver': function()
    {
        this._body.roll();
    },

    'private _moveFrontLeg': function( leg )
    {
        this._legs.front[ leg ].move();
    },

    'private _moveBackLeg': function( leg )
    {
        this._legs.back[ leg ].move();
    },

    // ...
} );
Figure: Encapsulating behavior of a class

As you can see above, the act of making the dog move forward is a bit more complicated than the developer may have originally expected. The dog has four separate legs that need to be moved individually. The dog must also first stand before it can be walked, but it can only stand if it's sitting. Detailed tasks such as these occur all the time in classes, but they are hidden from the developer using the public API. The developer should not be concerned with all of the legs. Worrying about such details brings the developer outside of the problem domain and into a new problem domain - how to get the dog to walk.

Private Members
---------------

Let's first explore private members. The majority of the members in the DOG class (see f:encapsulation) are private. This is the lowest level of visibility (and consequently the highest level of encapsulation). By convention, we prefix private members with an underscore. Private members are available only to the class that defined it and are not available outside the class.

    var dog = Dog();
    dog._moveFrontLeg( 1 );

    // TypeError: Object #<Dog> has no method '_moveFrontLeg'
Figure: Cannot access private members outside the class

You will notice that the dog's legs are declared private as well (see f:encapsulation). This is to ensure we look at the dog as a whole; we don't care about what the dog is made up of. Legs, fur, tail, teeth, tongue, etc - they are all irrelevant to our purpose. We just want to walk the dog. Encapsulating those details also ensures that they will not be tampered with, which will keep the dog in a consistent, predictable state.

Private members cannot be inherited. Let's say we want to make a class called TWOLEGGEDDOG to represent a dog that was trained to walk only on two feet.  We could approach this in a couple different ways. The first way would be to prevent the front legs from moving. What happens when we explore that approach:

    var two_legged_dog = Class( 'TwoLeggedDog' ).extend( Dog,
    {
        /**
         * This won't override the parent method.
         */
        'private _moveFrontLeg': function( leg )
        {
            // don't do anything
            return;
        },
    } )();

    two_legged_dog.walk();
Figure: Cannot override private members of supertype

If you were to attempt to walk a TWOLEGGEDDOG, you would find that the dog's front legs still move! This is because, as mentioned before, private methods are not inherited. Rather than overriding the parent's _MOVEFRONTLEG method, you are instead defining a new method, with the name _MOVEFRONTLEG. The old method will still be called.  Instead, we would have to override the public WALK method to prevent our dog from moving his front feet.

Protected Members
-----------------

Protected members are often misunderstood. Many developers will declare all of their members as either public or protected under the misconception that they may as well allow subclasses to override whatever functionality they want. This makes the class more flexible.

While it is true that the class becomes more flexible to work with for subtypes, this is a dangerous practice. In fact, doing so violates encapsulation.  Let's reconsider the levels of visibility in this manner:

public
     Provides an API for users of the class.

protected
     Provides an API for subclasses.

private
     Provides an API for the class itself.

Just as we want to hide data from the public API, we want to do the same for subtypes. If we simply expose all members to any subclass that comes by, that acts as a peephole in our black box. We don't want people spying into our internals. Subtypes shouldn't care about the dog's implementation either.

Private members should be used whenever possible, unless you are looking to provide subtypes with the ability to access or override methods. In that case, we can move up to try protected members.  Remember not to make a member public unless you wish it to be accessible to the entire world.

DOG (see f:encapsulation) defined a single method as protected - stand(). Because the method is protected, it can be inherited by subtypes. Since it is inherited, it may also be overridden. Let's define another subtype, LAZYDOG, which refuses to stand.

    var lazy_dog = Class( 'LazyDog' ).extend( Dog,
    {
        /**
         * Overrides parent method
         */
         'protected stand': function()
         {
            // nope!
            this.rollOver();
            return false;
         },
    } )();

    lazy_dog.walk();
Figure: Protected members are inherited by subtypes

There are a couple important things to be noted from the above example. Firstly, we are able to override the walk() method, because it was inherited.  Secondly, since rollOver() was also inherited from the parent, we are able to call that method, resulting in an upside-down dog that refuses to stand up, just moving his feet.

Another important detail to notice is that Dog.rollOver() accesses a private property of DOG -- _BODY. Our subclass does not have access to that variable. Since it is private, it was not inherited. However, since the rollOver() method is called within the context of the DOG class, the method has access to the private member, allowing our dog to successfully roll over. If, on the other hand, we were to override rollOver(), our code would not have access to that private object.  Calling this.__super() from within the overridden method would, however, call the parent method, which would again have access to its parent's private members.