[#5] Committed forgotten mkeywords.texi (oops
parent
cef0c0146b
commit
a022b62f8f
|
@ -0,0 +1,394 @@
|
|||
@c This document is part of the ease.js manual
|
||||
@c Copyright (c) 2011 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
|
||||
@c or any later version published by the Free Software Foundation;
|
||||
@c with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
|
||||
@c Texts. A copy of the license is included in the section entitled ``GNU
|
||||
@c Free 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.
|
||||
@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}.
|
||||
@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.
|
||||
|
||||
@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.
|
Loading…
Reference in New Issue