1
0
Fork 0

[#5] Reworked member keywords and member visibility section in manual

closure/master
Mike Gerwitz 2011-11-15 22:14:32 -05:00
parent fae30c877a
commit 6c379968bc
3 changed files with 60 additions and 312 deletions

View File

@ -71,8 +71,6 @@ ease.js, until such a point where prototypes are no longer adequate.
@menu @menu
* Defining Classes:: Learn how to define a class with ease.js * Defining Classes:: Learn how to define a class with ease.js
* Inheritance:: Extending classes from another * Inheritance:: Extending classes from another
* Member Visibility:: Encapsulation is a core concept of Object-Oriented
programming
* Static Members:: Members whose use do not require instantiation * Static Members:: Members whose use do not require instantiation
@end menu @end menu
@ -149,50 +147,31 @@ determines what type of member will be declared.
@var{name} must be unique across all members of @var{dfn}. @var{name} must be unique across all members of @var{dfn}.
@end enumerate @end enumerate
@anchor{mkeywords} @subsection Member Validations
@subsection Member Keywords For any member @var{name}:
@float Table, t:keywords @itemize
@multitable @columnfractions .10 .9 @item
@headitem Keyword @tab Description @var{keywords} of member @var{name} may contain only one access modifier
@item @code{public} (@pxref{Access Modifiers}).
@tab Access modifier. Places member @var{name} in public interface (accessible @item
outside of @var{C} or instance of @var{C}; accessible by subtypes). Implied if See @ref{Member Keywords,,Member Keywords} for @var{keywords} restrictions.
no other access modifier is provided. May not be used in conjunction with other @end itemize
access modifiers. @xref{Member Visibility}.
@item @code{protected}
@tab Access modifier. Places member @var{name} in protected interface
(accessible only within @var{C} or instance of @var{C}; accessible by subtypes).
May not be used in conjunction with other access modifiers. @xref{Member
Visibility}.
@item @code{private}
@tab Access modifier. Places member @var{name} in private interface (accessible
only within @var{C} or instance of @var{C}; not accessible by subtypes). May not
be used in conjunction with other access modifiers. @xref{Member Visibility}.
@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
Only the keywords in @ref{t:keywords} are supported as valid tokens within
@var{keywords}.
For any member @var{name} declared as a @emph{method}, the following must hold
true:
@itemize
@item
@var{keywords} of member @var{name} may not contain
@ref{Member Keywords,,@code{override}} without a super method of the same
@var{name} (@pxref{Inheritance}).
@item
@var{keywords} of member @var{name} may contain both
@ref{Member Keywords,,@code{static}} and @ref{Member Keywords,,@code{virtual}}
keywords (@pxref{Static Members} and @ref{Inheritance}).
@item
@var{keywords} of member @var{name} may not contain the
@ref{Member Keywords,,@code{const}} keyword.
@end itemize
@subsection Discussion @subsection Discussion
In @ref{f:class-js}, we saw how one would conventionally declare a class-like In @ref{f:class-js}, we saw how one would conventionally declare a class-like
@ -519,18 +498,19 @@ For any positive condition @var{o\_n} where member @var{n} is defined as a
One of the following conditions must always be true: One of the following conditions must always be true:
@itemize @itemize
@item @item
@var{dfn\_n\^C} is declared with the @ref{mkeywords,,@code{virtual}} keyword @var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{virtual}}
and @var{dfn\_n\^C'} is declared with the @ref{mkeywords,,@code{override}} keyword and @var{dfn\_n\^C'} is declared with the
keyword. @ref{Member Keywords,,@code{override}} keyword.
@itemize @itemize
@item @item
Note that @var{dfn\_n\^C'} will not become @ref{mkeywords,,@code{virtual}} Note that @var{dfn\_n\^C'} will not become @ref{Member
by default (unlike languages such as C++); they must be explicitly declared Keywords,,@code{virtual}} by default (unlike languages such as C++); they
as such. must be explicitly declared as such.
@end itemize @end itemize
@item @item
@var{dfn\_n\^C} is declared with the @ref{mkeywords,,@code{abstract}} keyword and @var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{abstract}}
@var{dfn\_n\^C'} omits the @ref{mkeywords,,@code{override}} keywords. keyword and @var{dfn\_n\^C'} omits the @ref{Member Keywords,,@code{override}}
keywords.
@end itemize @end itemize
@item @item
The argument count of method @var{dfn\_n\^C'} must be >= the argument count of The argument count of method @var{dfn\_n\^C'} must be >= the argument count of
@ -547,8 +527,8 @@ A method is said to be @dfn{concrete} when it provides a definition and
Any method @var{n} such that @var{dfn\_n\^C} is declared @code{abstract} may Any method @var{n} such that @var{dfn\_n\^C} is declared @code{abstract} may
be overridden by a concrete or abstract method @var{dfn\_n\^C'}. be overridden by a concrete or abstract method @var{dfn\_n\^C'}.
@item @item
A method @var{n} may @emph{not} be declared @ref{mkeywords,,@code{abstract}} A method @var{n} may @emph{not} be declared @ref{Member
if @var{dfn\_n\^C} is concrete. Keywords,,@code{abstract}} if @var{dfn\_n\^C} is concrete.
@end itemize @end itemize
@item @item
Member @var{dfn\_n\^C} must be a method. Member @var{dfn\_n\^C} must be a method.
@ -585,7 +565,7 @@ of the two. We describe inheritance as an ``is a'' relationship. That is:
@end itemize @end itemize
Subtypes @dfn{inherit} all public and protected members of their supertypes Subtypes @dfn{inherit} all public and protected members of their supertypes
(@pxref{Member Visibility}). This means that, in the case of our above example, (@pxref{Access Modifiers}). This means that, in the case of our above example,
the @code{walk()} and @code{bark()} methods would be available to our subtypes. the @code{walk()} and @code{bark()} methods would be available to our subtypes.
If the subtype also defines a method of the same name, as was done above, it If the subtype also defines a method of the same name, as was done above, it
will @dfn{override} the parent functionality. For now, we will limit our will @dfn{override} the parent functionality. For now, we will limit our
@ -659,6 +639,7 @@ conciseness.
* Overriding Methods:: Overriding inherited methods * Overriding Methods:: Overriding inherited methods
* Type Checks and Polymorphism:: Substituting similar classes for * Type Checks and Polymorphism:: Substituting similar classes for
one-another one-another
* Visibility Escalation:: Increasing visibility of inherited members
@end menu @end menu
@node Understanding Member Inheritance @node Understanding Member Inheritance
@ -677,15 +658,15 @@ public members. We will be focusing on public members in this chapter.
@item Protected API @item Protected API
Protected members make up a protected API, which is an API available to Protected members make up a protected API, which is an API available to
subclasses but @emph{not} the outside world. This is discussed more in the subclasses but @emph{not} the outside world. This is discussed more in the
Member Visibility section (@pxref{Member Visibility}), so we're going to leave Access Modifiers section (@pxref{Access Modifiers}), so we're going to leave
this untouched for now. this untouched for now.
@end table @end table
When a subtype inherits a member from its parent, it acts almost as if that When a subtype inherits a member from its parent, it acts almost as if that
member was defined in the class itself@footnote{This statement is not to imply member was defined in the class itself@footnote{This statement is not to imply
that inheritance is a case of copy-and-paste. There are slight variations, which that inheritance is a case of copy-and-paste. There are slight variations, which
are discussed in more detail in the Member Visibility section (@pxref{Member are discussed in more detail in the Access Modifiers section (@pxref{Access
Visibility}).}. This means that the subtype can use the inherited members as if Modifiers}).}. This means that the subtype can use the inherited members as if
they were its own (keep in mind that members also include properties). This they were its own (keep in mind that members also include properties). This
means that we @emph{do not} have to redefine the members in order to use them means that we @emph{do not} have to redefine the members in order to use them
ourselves. ourselves.
@ -884,255 +865,6 @@ Currently, it is necessary to perform this type check yourself. In future
versions, ease.js will allow for argument type hinting/strict typing, which will versions, ease.js will allow for argument type hinting/strict typing, which will
automate this check for you. automate this check for you.
@node Member Visibility
@section Member Visibility
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 prototype
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 can be
messy. ease.js aims to provide an elegant implementation that is both a pleasure
to work with and able to support protected members.
The term @dfn{visibility} refers to how a class member may be accessed. There
are three levels of visibility implemented by ease.js, which are listed here
from most visible to least:
@table @dfn
@item public
Accessible outside of the instance (e.g. @samp{foo.publicProp}). Inherited by
subtypes.
@item protected
Not accessible outside of the instance (only accessible by
@samp{this.protectedProp}). Inherited by subtypes.
@item private
Not accessible outside of the instance. Not inherited by subtypes.
@end table
By default, all members are public. This means that the members can be accessed
and modified from within an instance and from outside. Subtypes (classes that
inherit from it) 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
rest of this section.
@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
do not care @emph{how} they do their job. Encapsulation takes the complexity out
of a situation and allows the developer to focus on accomplishing the task using
familiar concepts. 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. By preventing the details of the
method from being exposed, we present the developer with a very simple
interface. 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.
Let's consider our @var{Dog} class in more detail:
@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. Why should the developer be concerned with all
of the legs?
As a general rule of thumb, you should use the @emph{lowest} level of visibility
possible unless you have a strong reason for increasing it. Start by declaring
everything as private and work from there.
@menu
* Private Members::
* Protected Members::
* Visibility Escalation:: Increasing visibility of inherited members
@end menu
@node Private Members
@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.
@node Protected Members
@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 (@pxref{Private Members,Private}) 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.
@node Visibility Escalation @node Visibility Escalation
@subsection Visibility Escalation @subsection Visibility Escalation
@dfn{Visibility escalation} is the act of increasing the visibility of a member. @dfn{Visibility escalation} is the act of increasing the visibility of a member.
@ -1433,7 +1165,7 @@ as any static access, references the exact same value. This is especially
important for objects and arrays. important for objects and arrays.
One important difference between other languages, such as PHP, is that ease.js One important difference between other languages, such as PHP, is that ease.js
supports the @ref{Member Visibility, visibility modifiers} in conjunction with supports the @ref{Access Modifiers, visibility modifiers} in conjunction with
the @code{const} keyword. That is, you can have public, protected and private the @code{const} keyword. That is, you can have public, protected and private
constants. Constants are public by default, like every other type of member. constants. Constants are public by default, like every other type of member.
This feature permits encapsulating constant values, which is important if you This feature permits encapsulating constant values, which is important if you

View File

@ -30,10 +30,19 @@ body, .float-caption
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Courier, monospace; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Courier, monospace;
} }
table {
width: 100%;
}
table, table td { table, table td {
padding: 0.25em; padding: 0.25em;
} }
table ul {
margin: 0px;
padding: 0px 0px 0px 1em;
}
dfn { dfn {
font-style: italic; font-style: italic;
font-weight: bold; font-weight: bold;
@ -95,6 +104,11 @@ sub.left {
margin-top: 0px; margin-top: 0px;
} }
/* unnecessary addition by texinfo */
.float table td br {
display: none;
}
.float-caption { .float-caption {
margin: 0px; margin: 0px;
padding: 0.5em; padding: 0.5em;

View File

@ -43,6 +43,7 @@ Free Documentation License".
* About:: About the project * About:: About the project
* Integration:: How to integrate ease.js into your project * Integration:: How to integrate ease.js into your project
* Classes:: Learn to work with Classes * Classes:: Learn to work with Classes
* Member Keywords:: Control member visibility and more.
* Source Tree:: Overview of source tree * Source Tree:: Overview of source tree
* Implementation Details:: The how and why of ease.js * Implementation Details:: The how and why of ease.js
* License:: Document License * License:: Document License
@ -56,6 +57,7 @@ Free Documentation License".
@include about.texi @include about.texi
@include integration.texi @include integration.texi
@include classes.texi @include classes.texi
@include mkeywords.texi
@include source-tree.texi @include source-tree.texi
@include impl-details.texi @include impl-details.texi
@include license.texi @include license.texi