diff --git a/doc/classes.texi b/doc/classes.texi index 0aa82f6..61eb513 100644 --- a/doc/classes.texi +++ b/doc/classes.texi @@ -71,8 +71,6 @@ ease.js, until such a point where prototypes are no longer adequate. @menu * Defining Classes:: Learn how to define a class with ease.js * 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 @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}. @end enumerate -@anchor{mkeywords} -@subsection Member Keywords -@float Table, t:keywords -@multitable @columnfractions .10 .9 -@headitem Keyword @tab Description -@item @code{public} -@tab Access modifier. 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. May not be used in conjunction with other -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}. +@subsection Member Validations +For any member @var{name}: +@itemize +@item +@var{keywords} of member @var{name} may contain only one access modifier +(@pxref{Access Modifiers}). +@item +See @ref{Member Keywords,,Member Keywords} for @var{keywords} restrictions. +@end itemize +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 In @ref{f:class-js}, we saw how one would conventionally declare a class-like @@ -259,9 +238,9 @@ capital, for class names (and nothing else). @menu * Anonymous vs. Named Classes:: -* Constructors:: How to declare a constructor -* Temporary Classes:: Throwaway classes that only need to be used once -* Temporary Instances:: Throwaway instances that only need to be used once +* Constructors:: How to declare a constructor +* Temporary Classes:: Throwaway classes that only need to be used once +* Temporary Instances:: Throwaway instances that only need to be used once @end menu @node Anonymous vs. Named Classes @@ -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: @itemize @item - @var{dfn\_n\^C} is declared with the @ref{mkeywords,,@code{virtual}} keyword - and @var{dfn\_n\^C'} is declared with the @ref{mkeywords,,@code{override}} - keyword. + @var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{virtual}} + keyword and @var{dfn\_n\^C'} is declared with the + @ref{Member Keywords,,@code{override}} keyword. @itemize @item - Note that @var{dfn\_n\^C'} will not become @ref{mkeywords,,@code{virtual}} - by default (unlike languages such as C++); they must be explicitly declared - as such. + Note that @var{dfn\_n\^C'} will not become @ref{Member + Keywords,,@code{virtual}} by default (unlike languages such as C++); they + must be explicitly declared as such. @end itemize @item - @var{dfn\_n\^C} is declared with the @ref{mkeywords,,@code{abstract}} keyword and - @var{dfn\_n\^C'} omits the @ref{mkeywords,,@code{override}} keywords. + @var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{abstract}} + keyword and @var{dfn\_n\^C'} omits the @ref{Member Keywords,,@code{override}} + keywords. @end itemize @item 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 be overridden by a concrete or abstract method @var{dfn\_n\^C'}. @item - A method @var{n} may @emph{not} be declared @ref{mkeywords,,@code{abstract}} - if @var{dfn\_n\^C} is concrete. + A method @var{n} may @emph{not} be declared @ref{Member + Keywords,,@code{abstract}} if @var{dfn\_n\^C} is concrete. @end itemize @item 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 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. 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 @@ -659,6 +639,7 @@ conciseness. * Overriding Methods:: Overriding inherited methods * Type Checks and Polymorphism:: Substituting similar classes for one-another +* Visibility Escalation:: Increasing visibility of inherited members @end menu @node Understanding Member Inheritance @@ -677,15 +658,15 @@ public members. We will be focusing on public members in this chapter. @item Protected API 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 -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. @end table 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 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 -Visibility}).}. This means that the subtype can use the inherited members as if +are discussed in more detail in the Access Modifiers section (@pxref{Access +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 means that we @emph{do not} have to redefine the members in order to use them 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 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 # 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 @subsection Visibility Escalation @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. 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 constants. Constants are public by default, like every other type of member. This feature permits encapsulating constant values, which is important if you diff --git a/doc/manual.css b/doc/manual.css index 92673c2..3d904a5 100644 --- a/doc/manual.css +++ b/doc/manual.css @@ -30,10 +30,19 @@ body, .float-caption font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Courier, monospace; } +table { + width: 100%; +} + table, table td { padding: 0.25em; } +table ul { + margin: 0px; + padding: 0px 0px 0px 1em; +} + dfn { font-style: italic; font-weight: bold; @@ -95,6 +104,11 @@ sub.left { margin-top: 0px; } +/* unnecessary addition by texinfo */ +.float table td br { + display: none; +} + .float-caption { margin: 0px; padding: 0.5em; diff --git a/doc/manual.texi b/doc/manual.texi index 58a3a41..9300d13 100644 --- a/doc/manual.texi +++ b/doc/manual.texi @@ -43,6 +43,7 @@ Free Documentation License". * About:: About the project * Integration:: How to integrate ease.js into your project * Classes:: Learn to work with Classes +* Member Keywords:: Control member visibility and more. * Source Tree:: Overview of source tree * Implementation Details:: The how and why of ease.js * License:: Document License @@ -56,6 +57,7 @@ Free Documentation License". @include about.texi @include integration.texi @include classes.texi +@include mkeywords.texi @include source-tree.texi @include impl-details.texi @include license.texi