413 lines
16 KiB
Plaintext
413 lines
16 KiB
Plaintext
@c This document is part of the GNU ease.js manual.
|
|
@c Copyright (C) 2011, 2013, 2014 Mike Gerwitz
|
|
@c Permission is granted to copy, distribute and/or modify this document
|
|
@c under the terms of the GNU Free Documentation License, Version 1.3 or
|
|
@c any later version published by the Free Software Foundation; with no
|
|
@c Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
|
|
@c A copy of the license is included in the section entitled ``GNU Free
|
|
@c Documentation License''.
|
|
|
|
@node Member Keywords
|
|
@chapter Member Keywords
|
|
Keywords are defined within the context of the @dfn{definition object}
|
|
(@pxref{dfnobj,,Definition Object}). In the sections that follow, let
|
|
@var{C} denote a class that contains the definition object @var{dfn}, which
|
|
in turn contains @var{keywords} within the declaration of method @var{name},
|
|
whose definition is denoted by @var{value}.
|
|
|
|
The table below summarizes the available keywords accepted by
|
|
@var{keywords}.
|
|
|
|
@float Table, t:keywords
|
|
@multitable @columnfractions .10 .90
|
|
@headitem Keyword @tab Description
|
|
@item @code{public}
|
|
@tab Places member @var{name} into the public API for @var{C} (@pxref{Access
|
|
Modifiers}).
|
|
@item @code{protected}
|
|
@tab Places member @var{name} into the protected API for @var{C}
|
|
(@pxref{Access Modifiers}).
|
|
@item @code{private}
|
|
@tab Places member @var{name} into the private API for @var{C}
|
|
(@pxref{Access Modifiers}).
|
|
@item @code{static}
|
|
@tab Binds member @var{name} to class @var{C} rather than instance of
|
|
@var{C}. Member data shared with each instance of type @var{C}.
|
|
@xref{Static Members}.
|
|
@item @code{abstract}
|
|
@tab Declares member @var{name} and defers definition to subtype.
|
|
@var{value} is interpreted as an argument list and must be of type
|
|
@code{array}. May only be used with methods. Member @var{name} must be part
|
|
of @var{dfn} of either an
|
|
@code{Interface} or @code{AbstractClass}. @xref{Abstract Members}.
|
|
@item @code{const}
|
|
@tab Defines an immutable property @var{name}. May not be used with methods
|
|
or getters/setters. @xref{Constants}.
|
|
@item @code{virtual}
|
|
@tab Declares that method @var{name} may be overridden by subtypes. Methods
|
|
without this keyword may not be overridden. May only be used with methods.
|
|
@xref{Inheritance}.
|
|
@item @code{override}
|
|
@tab Overrides method @var{name} of supertype of @var{C} with @var{value}.
|
|
May only override virtual methods. May only be used with methods.
|
|
@xref{Inheritance}.
|
|
@item @code{proxy}
|
|
@tab Proxies calls to method @var{name} to the object stored in property
|
|
@var{value}.
|
|
@end multitable
|
|
@caption{Supported keywords}
|
|
@end float
|
|
|
|
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 @ref{t:keywords}.
|
|
|
|
@menu
|
|
* Access Modifiers:: Control the context in which members may be accessed
|
|
@end menu
|
|
|
|
@node Access Modifiers
|
|
@section Access Modifiers
|
|
@dfn{Access modifiers}, when provided in @var{keywords}, alter the interface
|
|
into which the definition of member @var{name} is placed. There are three
|
|
interfaces, or levels of @dfn{visibility}, that dictate the context from
|
|
which a member may be accessed, listed here from the most permissive to the
|
|
least:
|
|
|
|
@table @dfn
|
|
@item public
|
|
Accessible outside of @var{C} or any instance of @var{C} (e.g.
|
|
@samp{foo.publicProp}). @ref{Inheritance,,Inherited} by subtypes of @var{C}.
|
|
|
|
@item protected
|
|
Not accessible outside of @var{C} or an instance of @var{C} (e.g.
|
|
@samp{this.protectedProp} within context of @var{C}).
|
|
@ref{Inheritance,,Inherited} by subtypes of
|
|
@var{C}.
|
|
|
|
@item private
|
|
Not accessible outside of @var{C} or any instance of @var{C}. @emph{Not}
|
|
@ref{Inheritance,,inherited} by subtypes of @var{C}.
|
|
@end table
|
|
|
|
@float Table, t:access-modifiers
|
|
@multitable @columnfractions .10 .90
|
|
@headitem Keyword @tab Description
|
|
@item @code{public}
|
|
@tab
|
|
Places member @var{name} in public interface (accessible outside of @var{C}
|
|
or instance of @var{C}; accessible by subtypes). Implied if no other access
|
|
modifier is provided.
|
|
@item @code{protected}
|
|
@tab
|
|
Places member @var{name} in protected interface (accessible only within
|
|
@var{C} or instance of @var{C}; accessible by subtypes).
|
|
@item @code{private}
|
|
@tab
|
|
Places member @var{name} in private interface (accessible only within
|
|
@var{C} or instance of @var{C}; not accessible by subtypes).
|
|
@end multitable
|
|
@caption{Access modifiers}
|
|
@end float
|
|
|
|
Access modifiers have the following properties:
|
|
|
|
@itemize
|
|
@item
|
|
Only one access modifier may appear in @var{keywords} for any given
|
|
@var{name}.
|
|
@item
|
|
If no access modifier is provided in @var{keywords} for any member
|
|
@var{name}, member @var{name} is implicitly @code{public}.
|
|
@end itemize
|
|
|
|
@menu
|
|
* Discussion: Access Modifiers Discussion. Uses and rationale
|
|
* Example: Access Modifiers Example. Demonstrating access modifiers
|
|
@end menu
|
|
|
|
@node Access Modifiers Discussion
|
|
@subsection 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 @dfn{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; @pxref{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.
|
|
|
|
@anchor{Encapsulation}
|
|
@subsubsection Encapsulation
|
|
@dfn{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 @emph{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 @var{Dog} which has a method
|
|
@code{walk()}. To walk a dog, we simply call @code{Dog().walk()}. The
|
|
@code{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 @var{Dog} relied on this state in
|
|
order to determine when the leg reached a certain position, but later
|
|
versions of @var{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@footnote{One would argue that this isn't
|
|
necessary a good thing. What if additional flexibility was needed?
|
|
@var{Dog}, in the sense of this example, can be thought of as a Facade
|
|
(GoF). One could provide more flexibility by composing @var{Dog} of, say,
|
|
@var{Leg} instances, a @var{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:
|
|
|
|
@itemize
|
|
@item
|
|
When attempting to determine the best access modifier (@pxref{Access
|
|
Modifiers}) to use for a member, start with the least level of visibility
|
|
(@code{private}) and work your way up if necessary.
|
|
@item
|
|
If your member is not private, be sure that you can justify your choice.
|
|
@itemize
|
|
@item
|
|
If protected - why do subclasses need access to that data? Is there a
|
|
better way to accomplish the same task without breaking encapsulation?
|
|
@item
|
|
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?
|
|
@end itemize
|
|
@end itemize
|
|
|
|
@node Access Modifiers Example
|
|
@subsection Example
|
|
Let's consider our @var{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 @var{Dog} similar to a wind-up toy:
|
|
|
|
@float Figure, f:encapsulation
|
|
@verbatim
|
|
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();
|
|
},
|
|
|
|
// ...
|
|
} );
|
|
@end verbatim
|
|
@caption{Encapsulating behavior of a class}
|
|
@end float
|
|
|
|
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 @emph{new} problem domain
|
|
- how to get the dog to walk.
|
|
|
|
|
|
@subsection Private Members
|
|
Let's first explore private members. The majority of the members in the
|
|
@var{Dog} class (@pxref{f:encapsulation,}) are private. This is the lowest
|
|
level of visibility (and consequently the @emph{highest} level of
|
|
encapsulation). By convention, we prefix private members with an underscore.
|
|
Private members are available @emph{only to the class that defined it} and
|
|
are not available outside the class.
|
|
|
|
@float Figure, f:encapsulation-call-priv
|
|
@verbatim
|
|
var dog = Dog();
|
|
dog._moveFrontLeg( 1 );
|
|
|
|
// TypeError: Object #<Dog> has no method '_moveFrontLeg'
|
|
@end verbatim
|
|
@caption{Cannot access private members outside the class}
|
|
@end float
|
|
|
|
You will notice that the dog's legs are declared private as well
|
|
(@pxref{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 @var{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:
|
|
|
|
|
|
@float Figure, f:encapsulation-inherit-priv
|
|
@verbatim
|
|
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();
|
|
@end verbatim
|
|
@caption{Cannot override private members of supertype}
|
|
@end float
|
|
|
|
If you were to attempt to walk a @var{TwoLeggedDog}, you would find that
|
|
@emph{the dog's front legs still move}! This is because, as mentioned
|
|
before, private methods are not inherited. Rather than overriding the
|
|
parent's @var{_moveFrontLeg} method, you are instead @emph{defining a new
|
|
method}, with the name @var{_moveFrontLeg}. The old method will still be
|
|
called. Instead, we would have to override the public @var{walk} method to
|
|
prevent our dog from moving his front feet.
|
|
|
|
@subsection 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 @emph{violates
|
|
encapsulation}. Let's reconsider the levels of visibility in this manner:
|
|
|
|
@table @strong
|
|
@item public
|
|
Provides an API for @emph{users of the class}.
|
|
|
|
@item protected
|
|
Provides an API for @emph{subclasses}.
|
|
|
|
@item private
|
|
Provides an API for @emph{the class itself}.
|
|
@end table
|
|
|
|
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.
|
|
|
|
@var{Dog} (@pxref{f:encapsulation,}) defined a single method as protected -
|
|
@code{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, @var{LazyDog}, which refuses to stand.
|
|
|
|
@float Figure, f:encapsulation-inherit-prot
|
|
@verbatim
|
|
var lazy_dog = Class( 'LazyDog' ).extend( Dog,
|
|
{
|
|
/**
|
|
* Overrides parent method
|
|
*/
|
|
'protected stand': function()
|
|
{
|
|
// nope!
|
|
this.rollOver();
|
|
return false;
|
|
},
|
|
} )();
|
|
|
|
lazy_dog.walk();
|
|
@end verbatim
|
|
@caption{Protected members are inherited by subtypes}
|
|
@end float
|
|
|
|
There are a couple important things to be noted from the above example.
|
|
Firstly, we are able to override the @code{walk()} method, because it was
|
|
inherited. Secondly, since @code{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 @code{Dog.rollOver()} accesses a
|
|
private property of @var{Dog} -- @var{_body}. Our subclass does not have
|
|
access to that variable. Since it is private, it was not inherited. However,
|
|
since the @code{rollOver()} method is called within the context of the
|
|
@var{Dog} class, the @emph{method} has access to the private member,
|
|
allowing our dog to successfully roll over. If, on the other hand, we were
|
|
to override @code{rollOver()}, our code would @emph{not} have access to that
|
|
private object. Calling @samp{this.__super()} from within the overridden
|
|
method would, however, call the parent method, which would again have access
|
|
to its parent's private members.
|