2013-12-22 09:37:21 -05:00
|
|
|
@c This document is part of the GNU ease.js manual.
|
2016-07-15 23:43:21 -04:00
|
|
|
@c Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016
|
|
|
|
@c Free Software Foundation, Inc.
|
|
|
|
@c
|
2011-03-14 00:13:43 -04:00
|
|
|
@c Permission is granted to copy, distribute and/or modify this document
|
2014-01-17 22:27:47 -05:00
|
|
|
@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''.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@node Classes
|
|
|
|
@chapter Working With Classes
|
2014-01-17 22:27:47 -05:00
|
|
|
In Object-Oriented programming, the most common term you are likely to
|
|
|
|
encounter is ``Class''. A @dfn{class} is like a blueprint for creating an
|
|
|
|
@dfn{object}, which is an @dfn{instance} of that class. Classes contain
|
|
|
|
@dfn{members}, which include primarily properties and methods. A
|
|
|
|
@dfn{property} is a value, much like a variable, that a class ``owns''. A
|
|
|
|
@dfn{method}, when comparing with JavaScript, is a function that is
|
|
|
|
``owned'' by a class. As a consequence, properties and methods are not part
|
|
|
|
of the global scope.
|
|
|
|
|
|
|
|
JavaScript does not support classes in the manner traditionally understood
|
|
|
|
by Object-Oriented programmers. This is because JavaScript follows a
|
|
|
|
different model which instead uses prototypes. Using this model, JavaScript
|
|
|
|
supports basic instantiation and inheritance. Rather than instantiating
|
|
|
|
classes, JavaScript instantiates constructors, which are functions. The
|
|
|
|
following example illustrates how you would typically create a class-like
|
|
|
|
object in JavaScript:
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@float Figure, f:class-js
|
|
|
|
@verbatim
|
2011-03-15 22:13:21 -04:00
|
|
|
/**
|
|
|
|
* Declaring "classes" WITHOUT ease.js
|
|
|
|
*/
|
|
|
|
|
2011-03-14 00:13:43 -04:00
|
|
|
// our "class"
|
|
|
|
var MyClass = function()
|
|
|
|
{
|
|
|
|
this.prop = 'foobar';
|
|
|
|
}
|
|
|
|
|
|
|
|
// a class method
|
|
|
|
MyClass.prototype.getProp = function()
|
|
|
|
{
|
|
|
|
return this.prop;
|
|
|
|
};
|
|
|
|
|
|
|
|
// create a new instance of the class and execute doStuff()
|
|
|
|
var foo = new MyClass();
|
2011-03-14 18:04:46 -04:00
|
|
|
console.log( foo.getProp() ); // outputs "foobar"
|
2011-03-14 00:13:43 -04:00
|
|
|
@end verbatim
|
2011-03-14 18:04:46 -04:00
|
|
|
@caption{Basic ``Class'' in JavaScript @emph{without} using ease.js}
|
2011-03-14 00:13:43 -04:00
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
This gets the job done, but the prototypal paradigm has a number of
|
|
|
|
limitations amongst its incredible flexibility. For Object-Oriented
|
|
|
|
programmers, it's both alien and inadequate. That is not to say that it is
|
|
|
|
not useful. In fact, it is so flexible that an entire Object-Oriented
|
|
|
|
framework was able to be built atop of it.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
ease.js aims to address the limitations of the prototype model and provide a
|
2014-01-17 22:27:47 -05:00
|
|
|
familiar environment for Object-Oriented developers. Developers should not
|
|
|
|
have to worry about @emph{how} classes are implemented in JavaScript
|
|
|
|
(indeed, those details should be encapsulated). You, as a developer, should
|
|
|
|
be concerned with only how to declare and use the classes. If you do not
|
|
|
|
understand what a prototype is, that should be perfectly fine. You shouldn't
|
|
|
|
need to understand it in order to use the library (though, it's always good
|
|
|
|
to understand what a prototype is when working with JavaScript).
|
|
|
|
|
|
|
|
In this chapter and those that follow, we will see the limitations that
|
|
|
|
ease.js addresses. We will also see how to declare the classes using both
|
|
|
|
prototypes and ease.js, until such a point where prototypes are no longer
|
|
|
|
adequate.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@menu
|
2011-11-20 00:24:53 -05:00
|
|
|
* Defining Classes:: Learn how to define a class with ease.js
|
|
|
|
* Inheritance:: Extending classes from another
|
|
|
|
* Static Members:: Members whose use do not require instantiation
|
2014-01-17 22:27:47 -05:00
|
|
|
* Abstract Members:: Declare members, deferring definition to subtypes
|
2014-01-06 00:20:06 -05:00
|
|
|
* Method Proxies:: Methods that proxy calls to another object
|
2011-03-14 00:13:43 -04:00
|
|
|
@end menu
|
|
|
|
|
|
|
|
|
2011-11-06 23:29:55 -05:00
|
|
|
@node Defining Classes
|
|
|
|
@section Defining Classes
|
|
|
|
@table @code
|
|
|
|
@item C = Class( string @var{name}, Object @var{dfn} )
|
|
|
|
Define named class @var{C} identified by @var{name} described by @var{dfn}.
|
|
|
|
|
|
|
|
@item C = Class( string @var{name} ).extend( Object @var{dfn} )
|
|
|
|
Define named class @var{C} identified by @var{name} described by @var{dfn}.
|
|
|
|
|
|
|
|
@item C = Class( Object @var{dfn} )
|
|
|
|
Define anonymous class @var{C} as described by @var{dfn}.
|
|
|
|
|
|
|
|
@item C = Class.extend( Object @var{dfn } )
|
|
|
|
Define anonymous class @var{C} as described by @var{dfn}.
|
|
|
|
@end table
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Class @var{C} can be defined in a number of manners, as listed above,
|
|
|
|
provided a @dfn{definition object} @var{dfn} containing the class members
|
|
|
|
and options. An optional string @var{name} may be provided to set an
|
|
|
|
internal identifier for @var{C}, which may be used for reflection and error
|
|
|
|
messages. If @var{name} is omitted, @var{C} will be declared anonymous.
|
2011-11-06 23:29:55 -05:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
@code{Class} must be imported (@pxref{Including}) from @code{easejs.Class};
|
|
|
|
it is not available in the global scope.
|
2011-11-06 23:29:55 -05:00
|
|
|
|
2011-11-10 23:44:32 -05:00
|
|
|
@anchor{dfnobj}
|
2011-11-06 23:29:55 -05:00
|
|
|
@subsection Definition Object
|
|
|
|
@table @code
|
|
|
|
@item dfn = @{ '[@var{keywords}] @var{name}': @var{value}[, ...] @}
|
2014-01-17 22:27:47 -05:00
|
|
|
Define definition object @var{dfn} containing a member identified by
|
|
|
|
@var{name}, described by optional @var{keywords} with the value of
|
|
|
|
@var{value}. The member type is determined by @code{typeof} @var{value}.
|
|
|
|
Multiple members may be provided in a single definition object.
|
2011-11-06 23:29:55 -05:00
|
|
|
@end table
|
|
|
|
|
|
|
|
The definition object @var{dfn} has the following properties:
|
|
|
|
@enumerate
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
The keys represent the @dfn{member declaration}, which may optionally
|
|
|
|
contain one or more @var{keywords} delimited by spaces. A space must delimit
|
|
|
|
the final keyword and @var{name}.
|
2011-11-06 23:29:55 -05:00
|
|
|
@enumerate
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{keywords} must consist only of recognized tokens, delimited by
|
|
|
|
spaces.
|
2011-11-06 23:29:55 -05:00
|
|
|
|
|
|
|
@item
|
|
|
|
Each token in @var{keywords} must be unique per @var{name}.
|
|
|
|
@end enumerate
|
|
|
|
|
|
|
|
@item
|
|
|
|
The @var{value} represents the @dfn{member definition}, the type of which
|
|
|
|
determines what type of member will be declared.
|
|
|
|
@enumerate
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
A @var{value} of type @code{function} will define a @dfn{method}, which is
|
|
|
|
an invokable member whose context is assigned to the class or class
|
|
|
|
instance depending on @var{keywords}.
|
2011-11-06 23:29:55 -05:00
|
|
|
|
|
|
|
@item
|
|
|
|
All other types of @var{value} will define a @dfn{property} - a mutable
|
|
|
|
value equal to @var{value}, assigned to a class or instance depending on
|
|
|
|
@var{keywords}. Properties may be made immutable using @var{keywords}.
|
|
|
|
|
|
|
|
@item
|
|
|
|
Getters/setters may be defined in an ECMAScript 5 or greater environment.
|
|
|
|
Getters/setters must share the same value for @var{keywords}.
|
|
|
|
@end enumerate
|
|
|
|
|
|
|
|
@item
|
|
|
|
@var{name} must be unique across all members of @var{dfn}.
|
|
|
|
@end enumerate
|
|
|
|
|
2011-11-15 22:14:32 -05:00
|
|
|
@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
|
2011-11-07 00:01:27 -05:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
For any member @var{name} declared as a @emph{method}, the following must
|
|
|
|
hold
|
2011-11-15 22:14:32 -05:00
|
|
|
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
|
2012-05-08 18:01:29 -04:00
|
|
|
@var{keywords} of member @var{name} may not contain both
|
2014-01-17 22:27:47 -05:00
|
|
|
@ref{Member Keywords,,@code{static}} and @ref{Member
|
|
|
|
Keywords,,@code{virtual}} keywords (@pxref{Static Members} and
|
|
|
|
@ref{Inheritance}).
|
2011-11-15 22:14:32 -05:00
|
|
|
@item
|
|
|
|
@var{keywords} of member @var{name} may not contain the
|
|
|
|
@ref{Member Keywords,,@code{const}} keyword.
|
2011-11-20 14:25:37 -05:00
|
|
|
@item
|
|
|
|
For any member @var{name} that contains the keyword
|
2014-01-17 22:27:47 -05:00
|
|
|
@ref{t:keywords,,@code{abstract}} in @var{keywords}, class @var{C} must
|
|
|
|
instead be declared as an @code{AbstractClass} (@pxref{Abstract Classes}).
|
2011-11-15 22:14:32 -05:00
|
|
|
@end itemize
|
2011-11-07 00:01:27 -05:00
|
|
|
|
2011-11-06 23:29:55 -05:00
|
|
|
@subsection Discussion
|
2014-01-17 22:27:47 -05:00
|
|
|
In @ref{f:class-js}, we saw how one would conventionally declare a
|
|
|
|
class-like object (a prototype) in JavaScript. This method is preferred for
|
|
|
|
many developers, but it is important to recognize that there is a distinct
|
|
|
|
difference between Prototypal and Classical Object-Oriented development
|
|
|
|
models. Prototypes lack many of the conveniences and features that are
|
|
|
|
provided by Classical languages, but they can be emulated with prototypes.
|
|
|
|
As an Object-Oriented developer, you shouldn't concern yourself with
|
|
|
|
@emph{how} a class is declared in JavaScript. In true OO fashion, that
|
|
|
|
behavior should be encapsulated. With ease.js, it is.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
Let's take a look at how to declare that exact same class using ease.js:
|
|
|
|
|
|
|
|
@float Figure, f:class-easejs
|
|
|
|
@verbatim
|
|
|
|
var Class = require( 'easejs' ).Class;
|
|
|
|
|
|
|
|
var MyClass = Class(
|
|
|
|
{
|
|
|
|
'public prop': 'foobar',
|
|
|
|
|
|
|
|
'public getProp': function()
|
|
|
|
{
|
|
|
|
return this.prop;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// create a new instance of the class and execute doStuff()
|
|
|
|
var foo = MyClass();
|
2011-03-14 18:04:46 -04:00
|
|
|
console.log( foo.getProp() ); // outputs "foobar"
|
2011-03-14 00:13:43 -04:00
|
|
|
@end verbatim
|
|
|
|
@caption{Basic anonymous class declaration using ease.js}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
That should look much more familiar to Object-Oriented developers. There are
|
|
|
|
a couple important notes before we continue evaluating this example:
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
The first thing you will likely notice is our use of the @code{public}
|
2016-07-15 13:40:15 -04:00
|
|
|
keyword;
|
|
|
|
this is optional (the default visibility is public);
|
|
|
|
it may be omitted for a more traditional JavaScript feel.
|
|
|
|
We will get more into visibility later on (@pxref{Access Modifiers}).
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@item
|
|
|
|
Unlike @ref{f:class-js,}, we do not use the @code{new} keyword in order to
|
2014-01-17 22:27:47 -05:00
|
|
|
instantiate our class. You are more than welcome to use the @code{new}
|
|
|
|
keyword if you wish, but it is optional when using ease.js. This is mainly
|
|
|
|
because without this feature, if the keyword is omitted, the constructor is
|
|
|
|
called as a normal function, which could have highly negative consequences.
|
|
|
|
This style of instantiation also has its benefits, which will be discussed
|
|
|
|
later on.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
ease.js's class module is imported using @code{require()} in the above
|
|
|
|
example. If using ease.js client-side (@pxref{Client-Side Include}), you
|
|
|
|
can instead use @samp{var Class = easejs.Class}. From this point on,
|
|
|
|
importing the module will not be included in examples.
|
2011-03-14 00:13:43 -04:00
|
|
|
@end itemize
|
|
|
|
|
|
|
|
The above example declares an anonymous class, which is stored in the
|
2014-01-17 22:27:47 -05:00
|
|
|
variable @var{MyClass}. By convention, we use CamelCase, with the first
|
|
|
|
letter capital, for class names (and nothing else).
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@menu
|
2016-07-15 23:35:36 -04:00
|
|
|
* Class Caveats:: Important things to note about using ease.js classes
|
2011-03-14 00:13:43 -04:00
|
|
|
* Anonymous vs. Named Classes::
|
2014-01-17 22:27:47 -05:00
|
|
|
* 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
|
2011-03-14 00:13:43 -04:00
|
|
|
@end menu
|
|
|
|
|
2016-07-15 23:35:36 -04:00
|
|
|
|
|
|
|
@node Class Caveats
|
|
|
|
@subsection Class Caveats
|
|
|
|
ease.js tries to make classes act as in traditional Classical@tie{}OOP
|
|
|
|
as much as possible,
|
|
|
|
but there are certain limitations,
|
|
|
|
especially when supporting ECMAScript@tie{}3.
|
|
|
|
These situations can cause some subtle bugs,
|
|
|
|
so it's important to note and understand them.
|
|
|
|
|
|
|
|
@subsubsection Returning Self
|
|
|
|
Returning @code{this} is a common practice for method
|
|
|
|
chaining.@footnote{
|
|
|
|
An interface that performs method chaining is less frequently
|
|
|
|
referred to as a ``fluent interface''.
|
|
|
|
This manual does not use that terminology.
|
|
|
|
Note also that method chaining implies that the class has state:
|
|
|
|
consider making your objects immutable instead,
|
|
|
|
which creates code that is easier to reason about.}
|
|
|
|
In the majority of cases, this works fine in ease.js
|
|
|
|
(see also @ref{Temporary Classes}):
|
|
|
|
|
|
|
|
@float Figure, f:method-chain
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
|
|
|
'public beginning': function()
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
'public middle': function()
|
|
|
|
{
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
|
|
|
|
'public end': function()
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
Foo().beginning().middle().end();
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using @code{this} for method chaining}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Within the context of the method, @code{this} is a reference to
|
|
|
|
the@tie{}privacy visibility object for that instance
|
|
|
|
(@pxref{The Visibility Object}).
|
|
|
|
That is---it exposes all of the object's internal state.
|
|
|
|
When it is returned from a method call, ease.js recognizes this and
|
|
|
|
replaces it with a reference to the @emph{public} visibility
|
|
|
|
object---the object that the rest of the world interacts with.
|
|
|
|
|
|
|
|
But what if you produce @code{this} in some other context?
|
|
|
|
A callback, for example:
|
|
|
|
|
|
|
|
@float Figure, f:method-this-callback
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
|
|
|
'private _foo': 'good',
|
|
|
|
|
|
|
|
'public beginning': function( c )
|
|
|
|
{
|
|
|
|
// XXX: `this' is the private visibility object
|
|
|
|
c( this );
|
|
|
|
},
|
|
|
|
|
|
|
|
'public end': function()
|
|
|
|
{
|
|
|
|
return this._foo;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// result: 'bad'
|
|
|
|
Foo()
|
|
|
|
.beginning( function( self )
|
|
|
|
{
|
|
|
|
// has access to internal state
|
|
|
|
self._foo = 'bad';
|
|
|
|
} )
|
|
|
|
.end();
|
|
|
|
@end verbatim
|
|
|
|
@caption{Accidentally revealing internal state via callback}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
In @ref{f:method-this-callback},
|
|
|
|
@code{beginning} applies the callback with a reference to what most
|
|
|
|
would believe to be the class instance
|
|
|
|
(which is a reasonable assumption,
|
|
|
|
considering that ease.js usually maintains that facade).
|
|
|
|
Since @code{this} is a reference to the private visibility object,
|
|
|
|
the callback has access to all its internal state,
|
|
|
|
and therefore the ability to set @code{_foo}.
|
|
|
|
|
|
|
|
To solve this problem,
|
|
|
|
use @code{this.__inst},
|
|
|
|
which is a reference to the @emph{public} visibility object
|
|
|
|
(the same one that ease.js would normally translate to on your
|
|
|
|
behalf):
|
|
|
|
|
|
|
|
@float Figure, f:method-callback-inst
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
|
|
|
'private _foo': 'good',
|
|
|
|
|
|
|
|
'public beginning': function( c )
|
|
|
|
{
|
|
|
|
// OK
|
|
|
|
c( this.__inst );
|
|
|
|
},
|
|
|
|
|
|
|
|
'public end': function()
|
|
|
|
{
|
|
|
|
return this._foo;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// result: 'good'
|
|
|
|
Foo()
|
|
|
|
.beginning( function( self )
|
|
|
|
{
|
|
|
|
// sets public property `_foo', since `self' is now the public
|
|
|
|
// visibility object
|
|
|
|
self._foo = 'bad';
|
|
|
|
} )
|
|
|
|
.end();
|
|
|
|
@end verbatim
|
|
|
|
@caption{Providing public visibility object using @code{this.__inst}}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
|
|
|
|
|
2011-03-14 00:13:43 -04:00
|
|
|
@node Anonymous vs. Named Classes
|
|
|
|
@subsection Anonymous vs. Named Classes
|
2014-01-17 22:27:47 -05:00
|
|
|
We state that @ref{f:class-easejs,} declared an @dfn{anyonmous class}
|
|
|
|
because the class was not given a name. Rather, it was simply assigned to a
|
|
|
|
variable, which itself has a name. To help keep this idea straight, consider
|
|
|
|
the common act of creating anonymous functions in JavaScript:
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@float Figure, f:anon-func
|
|
|
|
@verbatim
|
|
|
|
// anonymous
|
|
|
|
var myFunc = function() {};
|
|
|
|
|
|
|
|
// named
|
|
|
|
function myNamedFunc() {};
|
|
|
|
@end verbatim
|
|
|
|
@caption{Anonymous functions in JavaScript}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
If the function itself is not given a name, it is considered to be
|
|
|
|
anonymous, even though it is stored within a variable. Just as the engine
|
|
|
|
has no idea what that function is named, ease.js has no idea what the class
|
|
|
|
is named because it does not have access to the name of the variable to
|
|
|
|
which it was assigned.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
Names are not required for classes, but they are recommended. For example,
|
|
|
|
consider what may happen when your class is output in an error message.
|
|
|
|
|
|
|
|
@float Figure, f:anon-err
|
|
|
|
@verbatim
|
|
|
|
// call non-existent method
|
|
|
|
foo.baz();
|
|
|
|
|
|
|
|
// TypeError: Object #<anonymous> has no method 'baz'
|
|
|
|
@end verbatim
|
|
|
|
@caption{Anonymous classes do not make for useful error messages}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
If you have more than a couple classes in your software, that error message
|
|
|
|
is not too much help. You are left relying on the stack trace to track down
|
|
|
|
the error. This same output applies to converting a class to a string or
|
|
|
|
viewing it in a debugger. It is simply not helpful. If anything, it is
|
|
|
|
confusing. If you've debugged large JS applications that make liberal use of
|
|
|
|
anonymous functions, you might be able to understand that frustration.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Fortunately, ease.js permits you to declare a named class. A @dfn{named
|
|
|
|
class} is simply a class that is assigned a string for its name, so that
|
|
|
|
error messages, debuggers, etc provide more useful information. @emph{There
|
|
|
|
is functionally no difference between named and anonymous classes.}
|
2011-03-14 00:13:43 -04:00
|
|
|
|
|
|
|
@float Figure, f:class-named
|
|
|
|
@verbatim
|
|
|
|
var MyFoo = Class( 'MyFoo', {} ),
|
|
|
|
foo = MyFoo();
|
|
|
|
|
|
|
|
// call non-existent method
|
|
|
|
foo.baz();
|
|
|
|
|
|
|
|
// TypeError: Object #<MyFoo> has no method 'baz'
|
|
|
|
@end verbatim
|
|
|
|
@caption{Declaring an empty @emph{named} class}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Much better! We now have a useful error message and immediately know which
|
|
|
|
class is causing the issue.
|
2011-03-14 00:13:43 -04:00
|
|
|
|
2017-01-02 22:32:30 -05:00
|
|
|
|
2011-03-23 21:01:43 -04:00
|
|
|
@node Constructors
|
|
|
|
@subsection Constructors
|
2017-01-02 22:32:30 -05:00
|
|
|
ease.js defines object constructors in a manner consistent with many
|
|
|
|
other classical object-oriented languages:
|
|
|
|
a specially named method on the@tie{}class.
|
|
|
|
|
|
|
|
Traditionally, a ``constructor'' in JavaScript is a function intended to
|
|
|
|
initialize a new object when invoked with the@tie{}@code{new}
|
|
|
|
keyword.
|
|
|
|
That function also contains a @code{prototype} property that defines the
|
|
|
|
object's prototype.
|
|
|
|
ECMAScript@tie{}6,
|
|
|
|
with the introduction of the@tie{}@code{class} keyword,
|
|
|
|
provided a standard @code{constructor} method that generates
|
|
|
|
constructor for the prototype.
|
|
|
|
|
|
|
|
ease.js was written long before ES6 was ratified. The implementation
|
|
|
|
it chose is very similar to that of PHP's (@pxref{Constructor
|
|
|
|
Implementation}), using a @code{__construct}@tie{}method:
|
2011-03-23 21:01:43 -04:00
|
|
|
|
|
|
|
@float Figure, f:constructor
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
2015-09-16 00:37:24 -04:00
|
|
|
// may also use `construct`; see below
|
|
|
|
__construct: function( name )
|
2011-03-23 21:01:43 -04:00
|
|
|
{
|
|
|
|
console.log( 'Hello, ' + name + '!' );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// instantiate the class, invoking the constructor
|
|
|
|
Foo( 'World' );
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Hello, World!
|
|
|
|
@end verbatim
|
|
|
|
@caption{Declaring constructors using ease.js}
|
|
|
|
@end float
|
|
|
|
|
2015-09-16 00:37:24 -04:00
|
|
|
ease.js introduced the @code{constructor} method in version@tie{}0.2.7
|
2017-01-02 22:32:30 -05:00
|
|
|
to match the ES6 ``class'' implementation;
|
|
|
|
it is an alias for @code{__construct},
|
2017-01-02 23:38:09 -05:00
|
|
|
and they may be used interchangeably.@footnote{
|
|
|
|
With one caveat:
|
|
|
|
when referencing the constructor method from another method
|
|
|
|
(dare I ask why you would do such a thing?),
|
|
|
|
you must use e.g.@tie{}@samp{this.__construct};
|
|
|
|
using @samp{this.constructor} would not be very useful,
|
|
|
|
as this is always set (in JavaScript) to the function
|
|
|
|
that instantiated the object.}
|
2017-01-02 22:32:30 -05:00
|
|
|
This method name may also be used prior to ES6.
|
2015-09-16 00:37:24 -04:00
|
|
|
|
|
|
|
@float Figure, f:constructor-es6
|
|
|
|
@verbatim
|
|
|
|
// ECMAScript 6 syntax
|
|
|
|
let Foo = Class( 'Foo',
|
|
|
|
{
|
2017-01-02 22:32:30 -05:00
|
|
|
// you may still use __construct if you'd prefer, as shown above
|
2015-09-16 00:37:24 -04:00
|
|
|
constructor( name )
|
|
|
|
{
|
|
|
|
console.log( 'Hello, ' + name + '!' );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// instantiate the class, invoking the constructor
|
|
|
|
Foo( 'World' );
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Hello, World!
|
|
|
|
@end verbatim
|
|
|
|
@caption{Declaring constructors in an ECMAScript 6 style}
|
|
|
|
@end float
|
|
|
|
|
2017-01-02 22:32:30 -05:00
|
|
|
The constructor is invoked just after the class is instantiated,
|
|
|
|
allowing for necessary initialization tasks before the class can be
|
|
|
|
used.
|
|
|
|
It is good practice to use the constructor @emph{only} for
|
|
|
|
initialization that is critical to the object's integrity (such as
|
|
|
|
initializing internal state).
|
|
|
|
It is considered poor design for the constructor to have side-effects;
|
|
|
|
that is,
|
|
|
|
the act of instantiating an object should not manipulate global
|
|
|
|
variables or the environment.
|
|
|
|
|
|
|
|
The constructor operates exactly how you would expect a constructor to
|
|
|
|
in JavaScript,
|
|
|
|
with one important difference:
|
|
|
|
returning an object in the constructor does @emph{not} return that
|
|
|
|
object in place of the new class instance.
|
|
|
|
|
|
|
|
If you wish to prevent a class from being instantiated,
|
|
|
|
simply throw an exception within the constructor.
|
2011-03-23 21:35:25 -04:00
|
|
|
|
|
|
|
@float Figure, f:constructor-prevent
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
2017-01-02 22:32:30 -05:00
|
|
|
__construct: function( name )
|
2011-03-23 21:35:25 -04:00
|
|
|
{
|
|
|
|
throw Error( "Cannot instantiate class Foo" );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Prevent class from being instantiated}
|
|
|
|
@end float
|
|
|
|
|
2017-01-02 22:32:30 -05:00
|
|
|
Constructors are always public;
|
|
|
|
there is no harm in explicitly specifying the keyword,
|
|
|
|
but it's usually omitted for brevity.
|
|
|
|
It is not permitted to make a constructor protected or private
|
|
|
|
(@pxref{Access Modifiers}).
|
|
|
|
|
2017-01-02 23:28:28 -05:00
|
|
|
Unlike all other methods,
|
|
|
|
constructors are @ref{Member Keywords,,@code{virtual}} by default.
|
|
|
|
Many other languages (C++, Java, C#, and others) do not inherit
|
|
|
|
class constructors from their supertypes.
|
|
|
|
ease.js classes are prototypes,
|
|
|
|
and uninstantiated prototypes are functions (constructors),
|
|
|
|
so classes are effectively first-class objects in JavaScript.
|
|
|
|
Consequently,
|
|
|
|
they can be passed around and invoked like any other function,
|
|
|
|
which can be a convenient alternative to factories
|
|
|
|
(and a transparent alternative to functions that create objects).
|
|
|
|
It is therefore useful to have the constructor as part of the class's
|
|
|
|
public API.
|
|
|
|
However,
|
|
|
|
it is also important that subtypes always be able to override the
|
|
|
|
constructor (@pxref{Overriding Methods});
|
|
|
|
otherwise subtypes may not be able to initialize properly,
|
|
|
|
making for a very clumsy implementation.
|
|
|
|
|
2017-01-02 22:32:30 -05:00
|
|
|
Constructors are optional,
|
|
|
|
and no constructor is defined by default.@footnote{
|
|
|
|
That is, no user-facing constructor is defined by default;
|
|
|
|
ease.js handles plenty of its own internal tasks during
|
|
|
|
construction,
|
|
|
|
which cannot be overridden or inhibited.}
|
|
|
|
|
2011-03-23 21:01:43 -04:00
|
|
|
|
2011-03-15 00:31:09 -04:00
|
|
|
@node Temporary Classes
|
|
|
|
@subsection Temporary Classes
|
|
|
|
In @ref{f:class-easejs,}, we saw that the @code{new} keyword was unnecessary
|
2014-01-17 22:27:47 -05:00
|
|
|
when instantiating classes. This permits a form of shorthand that is very
|
|
|
|
useful for creating @dfn{temporary classes}, or ``throwaway`` classes which
|
|
|
|
are used only once.
|
2011-03-15 00:31:09 -04:00
|
|
|
|
|
|
|
Consider the following example:
|
|
|
|
|
|
|
|
@float Figure, f:class-tmp
|
|
|
|
@verbatim
|
|
|
|
// new instance of anonymous class
|
|
|
|
var foo = Class(
|
|
|
|
{
|
|
|
|
'public bar': function()
|
|
|
|
{
|
|
|
|
return 'baz';
|
|
|
|
}
|
|
|
|
} )();
|
|
|
|
|
|
|
|
foo.bar(); // returns 'baz'
|
|
|
|
@end verbatim
|
|
|
|
@caption{Declaring a temporary (throwaway) class}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
In @ref{f:class-tmp,} above, rather than declaring a class, storing that in
|
|
|
|
a variable, then instantiating it separately, we are doing it in a single
|
|
|
|
command. Notice the parenthesis at the end of the statement. This invokes
|
|
|
|
the constructor. Since the @code{new} keyword is unnecessary, a new instance
|
|
|
|
of the class is stored in the variable @var{foo}.
|
2011-03-15 00:31:09 -04:00
|
|
|
|
|
|
|
We call this a temporary class because it is used only to create a single
|
2014-01-17 22:27:47 -05:00
|
|
|
instance. The class is then never referenced again. Therefore, we needn't
|
|
|
|
even store it - it's throwaway.
|
2011-03-15 00:31:09 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
The downside of this feature is that it is difficult to notice unless the
|
|
|
|
reader is paying very close attention. There is no keyword to tip them off.
|
|
|
|
Therefore, it is very important to clearly document that you are storing an
|
|
|
|
instance in the variable rather than an actual class definition. If you
|
|
|
|
follow the CamelCase convention for class names, then simply do not
|
|
|
|
capitalize the first letter of the destination variable for the instance.
|
2011-03-15 00:31:09 -04:00
|
|
|
|
2011-03-15 21:31:51 -04:00
|
|
|
@node Temporary Instances
|
|
|
|
@subsection Temporary Instances
|
|
|
|
Similar to @ref{Temporary Classes,}, you may wish to use an @emph{instance}
|
2014-01-17 22:27:47 -05:00
|
|
|
temporarily to invoke a method or chain of methods. @dfn{Temporary
|
|
|
|
instances} are instances that are instantiated in order to invoke a method
|
|
|
|
or chain of methods, then are immediately discarded.
|
2011-03-15 21:31:51 -04:00
|
|
|
|
|
|
|
@float Figure, f:inst-tmp
|
|
|
|
@verbatim
|
|
|
|
// retrieve the name from an instance of Foo
|
|
|
|
var name = Foo().getName();
|
|
|
|
|
|
|
|
// method chaining
|
2014-01-17 22:27:47 -05:00
|
|
|
var car = VehicleFactory()
|
|
|
|
.createBody()
|
|
|
|
.addWheel( 4 )
|
|
|
|
.addDoor( 2 )
|
|
|
|
.build();
|
2011-03-15 21:31:51 -04:00
|
|
|
|
|
|
|
// temporary class with callback
|
|
|
|
HttpRequest( host, port ).get( path, function( data )
|
|
|
|
{
|
|
|
|
console.log( data );
|
|
|
|
} );
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
// Conventionally (without ease.js), you'd accomplish the above using
|
|
|
|
// the 'new' keyword. You may still do this with ease.js, though it is
|
|
|
|
// less clean looking.
|
2011-03-15 21:31:51 -04:00
|
|
|
( new Foo() ).someMethod();
|
|
|
|
@end verbatim
|
|
|
|
@caption{Declaring a temporary (throwaway) class}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Rather than storing the class instance, we are using it simply to invoke
|
2014-01-17 22:27:47 -05:00
|
|
|
methods. The results of those methods are stored in the variable rather than
|
|
|
|
the class instance. The instance is immediately discarded, since it is no
|
|
|
|
longer able to be referenced, and is as such a temporary instance.
|
2011-03-15 21:31:51 -04:00
|
|
|
|
|
|
|
In order for method chaining to work, each method must return itself.
|
|
|
|
|
|
|
|
This pattern is useful for when a class requires instantiation in order to
|
2014-01-17 22:27:47 -05:00
|
|
|
invoke a method. Classes that intend to be frequently used in this manner
|
|
|
|
should declare static methods so that they may be accessed without the
|
|
|
|
overhead of creating a new class instance.
|
2011-03-15 21:31:51 -04:00
|
|
|
|
2011-03-16 00:27:09 -04:00
|
|
|
|
2011-03-19 19:35:21 -04:00
|
|
|
@node Inheritance
|
|
|
|
@section Inheritance
|
2011-11-06 23:29:55 -05:00
|
|
|
@table @code
|
2014-01-17 22:27:47 -05:00
|
|
|
@item C' = Class( string @var{name} ).extend( Object @var{base}, Object
|
|
|
|
@var{dfn} ) Define named class @var{C'} identified by @var{name} as a
|
|
|
|
subtype of @var{base}, described by @var{dfn}. @var{base} may be of type
|
|
|
|
@code{Class} or may be any enumerable object.
|
2011-11-06 23:29:55 -05:00
|
|
|
|
|
|
|
@item C' = C.extend( Object @var{dfn} )
|
|
|
|
Define anonymous class @var{C'} as a subtype of class @var{C}, described by
|
|
|
|
@var{dfn}.
|
|
|
|
|
|
|
|
@item C' = Class.extend( Object @var{base}, Object @var{dfn} )
|
|
|
|
Define anonymous class @var{C'} as a subtype of @var{base}, described by
|
|
|
|
@var{dfn}. @var{base} may be of type @code{Class} or may be any enumerable
|
|
|
|
object.
|
|
|
|
@end table
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{C} is a class as defined in @ref{Defining Classes}. @var{base} may be
|
|
|
|
any class or object containing enumerable members. @var{dfn} is to be a
|
|
|
|
definition object as defined in @ref{dfnobj,,Definition Object}.
|
2011-11-09 00:10:52 -05:00
|
|
|
|
2011-11-27 22:53:43 -05:00
|
|
|
Provided non-final @var{C} or @var{base} to satisfy requirements of @var{C},
|
|
|
|
class @var{C'} will be defined as a @dfn{subtype} (child) of @dfn{supertype}
|
|
|
|
(parent) class @var{C}. Provided @var{base} that does @emph{not} satisfy
|
2014-01-17 22:27:47 -05:00
|
|
|
requirements of @var{C}, @var{C'} will be functionally equivalent to a
|
|
|
|
subtype of anonymous class @var{B} as defined by @var{B} =
|
|
|
|
Class( @var{base} ).
|
2011-11-09 00:10:52 -05:00
|
|
|
|
|
|
|
|
|
|
|
@subsection Member Inheritance
|
|
|
|
Let @var{dfn\_n\^c} denote a member of @var{dfn} in regards to class @var{c}
|
2014-01-17 22:27:47 -05:00
|
|
|
that matches (case-sensitive) name @var{n}. Let @var{o\_n} denote an
|
|
|
|
override, represented as boolean value that is true under the condition that
|
|
|
|
both @var{dfn\_n\^C'} and @var{dfn\_n\^C} are defined values.
|
2011-11-09 00:10:52 -05:00
|
|
|
|
|
|
|
@var{C'} will @dfn{inherit} all public and protected members of supertype
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{C} such that @var{dfn\_n\^C'} = @var{dfn\_n\^C} for each @var{dfn\^C}.
|
|
|
|
For any positive condition @var{o\_n}, member @var{dfn\_n\^C'} will be said
|
|
|
|
to @dfn{override} member @var{dfn\_n\^C}, provided that overriding member
|
|
|
|
@var{n} passes all validation rules associated with the operation. A
|
|
|
|
@code{protected} member may be @dfn{escalated} to @code{public}, but the
|
|
|
|
reverse is untrue. @code{private} members are invisible to
|
|
|
|
subtypes.@footnote{This is true conceptually, but untrue in pre-ES5
|
|
|
|
environments where ease.js is forced to fall back (@pxref{Private Member
|
|
|
|
Dilemma}). As such, one should always develop in an ES5 or later environment
|
|
|
|
to ensure visibility restrictions are properly enforced.}
|
2011-11-10 23:02:26 -05:00
|
|
|
|
|
|
|
For any positive condition @var{o\_n} where member @var{n} is defined as a
|
|
|
|
@emph{method}:
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
One of the following conditions must always be true:
|
|
|
|
@itemize
|
|
|
|
@item
|
2011-11-15 22:14:32 -05:00
|
|
|
@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.
|
2017-02-01 00:09:59 -05:00
|
|
|
@item
|
|
|
|
@var{dfn\_n\^C} is declared with the @ref{Member Keywords,,@code{override}}
|
|
|
|
keyword and @var{dfn\_n\^C'} is also declared with the
|
|
|
|
@ref{Member Keywords,,@code{override}} keyword.
|
2011-11-10 23:02:26 -05:00
|
|
|
@item
|
2015-10-17 00:09:29 -04:00
|
|
|
@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.
|
2011-11-10 23:02:26 -05:00
|
|
|
@end itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
The argument count of method @var{dfn\_n\^C'} must be >= the argument count
|
|
|
|
of method @var{dfn\_n\^C} to permit polymorphism.
|
2011-11-10 23:02:26 -05:00
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
A reference to super method @var{dfn\_n\^C} will be preserved and assigned
|
2015-05-27 23:22:40 -04:00
|
|
|
to @samp{this.__super} within context of method
|
|
|
|
@var{dfn\_n\^C'}.@footnote{Due to an @ref{Method
|
|
|
|
Wrapping,,implementation detail}, @samp{this.__super} may remain in
|
|
|
|
scope after invoking a private method; this behavior is undefined and
|
|
|
|
should not be relied on.}
|
2011-11-10 23:02:26 -05:00
|
|
|
@item
|
|
|
|
A method is said to be @dfn{concrete} when it provides a definition and
|
2011-11-10 23:44:32 -05:00
|
|
|
@dfn{abstract} when it provides only a declaration
|
|
|
|
(@pxref{dfnobj,,Definition Object}).
|
2011-11-10 23:02:26 -05:00
|
|
|
@itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
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'}.
|
2011-11-10 23:02:26 -05:00
|
|
|
@item
|
2015-10-17 00:09:29 -04:00
|
|
|
A method @var{n} may @emph{not} be declared
|
|
|
|
@ref{Member Keywords,,@code{abstract}} if @var{dfn\_n\^C} is concrete.
|
2011-11-10 23:02:26 -05:00
|
|
|
@end itemize
|
|
|
|
@item
|
2011-11-19 22:30:10 -05:00
|
|
|
Member @var{dfn\_n\^C'} must be a method.
|
2011-12-03 12:26:03 -05:00
|
|
|
@item
|
|
|
|
Member @var{dfn\_n\^C} must not have been declared @ref{Member
|
|
|
|
Keywords,,@code{private}} (@pxref{Private Member Dilemma}).
|
2011-11-10 23:02:26 -05:00
|
|
|
@end itemize
|
|
|
|
|
|
|
|
Members that have been declared @code{static} cannot be overridden
|
|
|
|
(@pxref{Static Members}).
|
2011-11-09 00:10:52 -05:00
|
|
|
|
|
|
|
@subsection Discussion
|
2014-01-17 22:27:47 -05:00
|
|
|
Inheritance can be a touchy subject among many Object-Oriented developers
|
|
|
|
due to encapsulation concerns and design considerations over method
|
|
|
|
overrides. The decision of whether or not inheritance is an appropriate
|
|
|
|
choice over composition is left to the developer; ease.js provides the
|
|
|
|
facilities for achieving classical inheritance where it is desired.
|
2011-03-19 19:35:21 -04:00
|
|
|
|
|
|
|
@float Figure, f:inheritance-ex
|
2011-03-20 15:08:21 -04:00
|
|
|
@image{img/inheritance-ex}
|
2011-03-19 19:35:21 -04:00
|
|
|
@caption{Basic inheritance example}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
In the above example, we would say that @var{LazyDog} and @var{TwoLeggedDog}
|
|
|
|
are @emph{subtypes} of @var{Dog}, and that @var{Dog} is the @emph{supertype}
|
2011-03-21 20:04:37 -04:00
|
|
|
of the two. We describe inheritance as an ``is a'' relationship. That is:
|
2011-03-20 18:12:37 -04:00
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
@var{LazyDog} is a @var{Dog}.
|
|
|
|
|
|
|
|
@item
|
|
|
|
@var{TwoLeggedDog} is also a @var{Dog}.
|
|
|
|
|
|
|
|
@item
|
|
|
|
@var{Dog} is @emph{not} a @var{LazyDog} or a @var{TwoLeggedDog}.
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
Subtypes @dfn{inherit} all public and protected members of their supertypes
|
2014-01-17 22:27:47 -05:00
|
|
|
(@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 discussion to public members. How would we represent these
|
|
|
|
classes using ease.js?
|
2011-03-20 18:12:37 -04:00
|
|
|
|
|
|
|
@float Figure, f:inheritance
|
|
|
|
@verbatim
|
|
|
|
// our parent class (supertype)
|
|
|
|
var Dog = Class( 'Dog',
|
|
|
|
{
|
2011-11-07 00:03:39 -05:00
|
|
|
'virtual public walk': function()
|
2011-03-20 18:12:37 -04:00
|
|
|
{
|
2011-03-20 18:49:40 -04:00
|
|
|
console.log( 'Walking the dog' );
|
|
|
|
},
|
|
|
|
|
|
|
|
'public bark': function()
|
|
|
|
{
|
|
|
|
console.log( 'Woof!' );
|
2011-03-20 18:12:37 -04:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// subclass (child), as a named class
|
|
|
|
var LazyDog = Class( 'LazyDog' ).extend( Dog,
|
|
|
|
{
|
2011-11-07 00:03:39 -05:00
|
|
|
'override public walk': function()
|
2011-03-20 18:12:37 -04:00
|
|
|
{
|
2011-03-20 18:49:40 -04:00
|
|
|
console.log( 'Lazy dog refuses to walk.' );
|
2011-03-20 18:12:37 -04:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// subclass (child), as an anonymous class
|
|
|
|
var TwoLeggedDog = Dog.extend(
|
|
|
|
{
|
2011-11-07 00:03:39 -05:00
|
|
|
'override public walk': function()
|
2011-03-20 18:12:37 -04:00
|
|
|
{
|
2011-03-20 18:49:40 -04:00
|
|
|
console.log( 'Walking the dog on two feet' );
|
2011-03-20 18:12:37 -04:00
|
|
|
}
|
|
|
|
} );
|
2017-02-01 00:09:59 -05:00
|
|
|
|
|
|
|
// supertype override is implicitly virtual
|
|
|
|
var ReallyLazyDog = LazyDog.extend(
|
|
|
|
{
|
|
|
|
'override public walk': function()
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
} );
|
2011-03-20 18:12:37 -04:00
|
|
|
@end verbatim
|
|
|
|
@caption{Inheritance in ease.js}
|
|
|
|
@end float
|
|
|
|
|
2017-02-01 00:09:59 -05:00
|
|
|
(The above inheritance tree is a bad idea and is for illustration
|
|
|
|
purposes only:
|
|
|
|
if you want to layer attributes,
|
|
|
|
use Traits or a composition-based pattern like Decorators.)
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
You should already understand how to define a class (@pxref{Defining
|
|
|
|
Classes}). The above example introduced two means of @dfn{extending}
|
|
|
|
classes -- defining a new class that inherits from a parent:
|
2011-03-20 18:12:37 -04:00
|
|
|
|
|
|
|
@table @strong
|
|
|
|
@item Named Subclasses
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{LazyDog} is defined as a @emph{named} subclass (@pxref{Anonymous vs.
|
|
|
|
Named Classes}). This syntax requires the use of @samp{Class( 'Name' )}. The
|
|
|
|
@code{extend()} method then allows you to extend from an existing class by
|
|
|
|
passing the class reference in as the first argument.
|
2011-03-20 18:12:37 -04:00
|
|
|
|
|
|
|
@item Anonymous Subclasses
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{TwoLeggedDog} was declared as an @emph{anonymous} subclass. The syntax
|
|
|
|
for this declaration is a bit more concise, but you forfeit the benefits of
|
|
|
|
named classes (@pxref{Anonymous vs. Named Classes}). In this case, you can
|
|
|
|
simply call the supertype's @code{extend()} method. Alternatively, you can
|
|
|
|
use the @samp{Class.extend( Base, @{@} )} syntax, as was used with the named
|
|
|
|
subclass @var{LazyDog}.
|
2011-03-20 18:12:37 -04:00
|
|
|
@end table
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
You are @emph{always} recommended to use the named syntax when declaring
|
|
|
|
classes in order to provide more useful error messages. If you are willing
|
|
|
|
to deal with the less helpful error messages, feel free to use anonymous
|
|
|
|
classes for their conciseness.
|
2011-03-19 19:35:21 -04:00
|
|
|
|
2011-03-20 18:49:40 -04:00
|
|
|
@menu
|
|
|
|
* Understanding Member Inheritance:: How to work with inherited members
|
2011-03-21 19:39:59 -04:00
|
|
|
* Overriding Methods:: Overriding inherited methods
|
2011-03-21 00:04:44 -04:00
|
|
|
* Type Checks and Polymorphism:: Substituting similar classes for
|
2014-01-17 22:27:47 -05:00
|
|
|
one-another
|
|
|
|
* Visibility Escalation:: Increasing visibility of inherited
|
|
|
|
members
|
2016-07-15 00:15:11 -04:00
|
|
|
* Error Subtypes:: Transparent Error subtyping
|
2011-11-27 22:53:43 -05:00
|
|
|
* Final Classes:: Classes that cannot be inherited from
|
2011-03-20 18:49:40 -04:00
|
|
|
@end menu
|
|
|
|
|
|
|
|
@node Understanding Member Inheritance
|
|
|
|
@subsection Understanding Member Inheritance
|
2014-01-17 22:27:47 -05:00
|
|
|
In @ref{f:inheritance}, we took a look at how to inherit from a parent
|
|
|
|
class. What does it mean when we ``inherit'' from a parent? What are we
|
|
|
|
inheriting? The answer is: the API.
|
2011-03-20 18:49:40 -04:00
|
|
|
|
|
|
|
There are two types of APIs that subtypes can inherit from their parents:
|
|
|
|
|
|
|
|
@table @emph
|
|
|
|
@item Public API
|
2014-01-17 22:27:47 -05:00
|
|
|
This is the API that is accessible to everyone using your class. It contains
|
|
|
|
all public members. We will be focusing on public members in this chapter.
|
2011-03-20 18:49:40 -04:00
|
|
|
|
|
|
|
@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
|
2011-11-15 22:14:32 -05:00
|
|
|
Access Modifiers section (@pxref{Access Modifiers}), so we're going to leave
|
2011-03-20 18:49:40 -04:00
|
|
|
this untouched for now.
|
|
|
|
@end table
|
|
|
|
|
|
|
|
When a subtype inherits a member from its parent, it acts almost as if that
|
2014-01-17 22:27:47 -05:00
|
|
|
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 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.
|
2011-03-20 18:49:40 -04:00
|
|
|
|
|
|
|
@var{LazyDog} and @var{TwoLeggedDog} both inherit the @code{walk()} and
|
2014-01-17 22:27:47 -05:00
|
|
|
@code{bark()} methods from the @var{Dog} supertype. Using @var{LazyDog} as
|
|
|
|
an example, let's see what happens when we attempt to use the @code{bark()}
|
|
|
|
method inherited from the parent.
|
2011-03-20 18:49:40 -04:00
|
|
|
|
|
|
|
@float Figure, f:using-inherited-members
|
|
|
|
@verbatim
|
|
|
|
var LazyDog = Class( 'LazyDog' ).extend( Dog,
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Bark when we're poked
|
|
|
|
*/
|
2013-12-25 02:04:30 -05:00
|
|
|
'virtual public poke': function()
|
2011-03-20 18:49:40 -04:00
|
|
|
{
|
|
|
|
this.bark();
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// poke() a new instance of LazyDog
|
|
|
|
LazyDog().poke();
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Woof!
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using inherited members}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
In @ref{f:using-inherited-members} above, we added a @code{poke()} method to
|
|
|
|
our @var{LazyDog} class. This method will call the @code{bark()} method that
|
|
|
|
was inherited from @var{Dog}. If we actually run the example, you will
|
|
|
|
notice that the dog does indeed bark, showing that we are able to call our
|
|
|
|
parent's method even though we did not define it ourselves.
|
2011-03-20 18:49:40 -04:00
|
|
|
|
2011-03-21 19:39:59 -04:00
|
|
|
@node Overriding Methods
|
|
|
|
@subsection Overriding Methods
|
2014-01-17 22:27:47 -05:00
|
|
|
When a method is inherited, you have the option of either keeping the
|
|
|
|
parent's implementation or overriding it to provide your own. When you
|
|
|
|
override a method, you replace whatever functionality was defined by the
|
|
|
|
parent. This concept was used to make our @var{LazyDog} lazy and our
|
|
|
|
@var{TwoLeggedDog} walk on two legs in @ref{f:inheritance}.
|
2011-03-21 19:39:59 -04:00
|
|
|
|
|
|
|
After overriding a method, you may still want to invoke the parent's method.
|
2011-03-21 20:04:37 -04:00
|
|
|
This allows you to @emph{augment} the functionality rather than replacing it
|
|
|
|
entirely. ease.js provides a magic @code{__super()} method to do this. This
|
2014-01-17 22:27:47 -05:00
|
|
|
method is defined only for the overriding methods and calls the parent
|
|
|
|
method that was overridden.
|
2011-03-21 20:04:37 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
In order to demonstrate this, let's add an additional subtype to our
|
|
|
|
hierarchy. @var{AngryDog} will be a subtype of @var{LazyDog}. Not only is
|
|
|
|
this dog lazy, but he's rather moody.
|
2011-03-21 19:39:59 -04:00
|
|
|
|
|
|
|
@float Figure, f:super-method
|
|
|
|
@verbatim
|
|
|
|
var AngryDog = Class( 'AngryDog' ).extend( LazyDog,
|
|
|
|
{
|
|
|
|
'public poke': function()
|
|
|
|
{
|
|
|
|
// augment the parent method
|
|
|
|
console.log( 'Grrrrrr...' );
|
|
|
|
|
|
|
|
// call the overridden method
|
|
|
|
this.__super();
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// poke a new AngryDog instance
|
|
|
|
AngryDog().poke();
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Grrrrrr...
|
|
|
|
// Woof!
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using @code{__super()} method}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
If you remember from @ref{f:using-inherited-members}, we added a
|
|
|
|
@code{poke()} method to @var{LazyDog}. In @ref{f:super-method} above, we are
|
|
|
|
overriding this method so that @var{AngryDog} growls when you poke him.
|
|
|
|
However, we still want to invoke @var{LazyDog}'s default behavior when he's
|
|
|
|
poked, so we also call the @code{__super()} method. This will also make
|
|
|
|
@var{AngryDog} bark like @var{LazyDog}.
|
2011-03-21 19:39:59 -04:00
|
|
|
|
|
|
|
It is important to note that @code{__super()} must be invoked like any other
|
2014-01-17 22:27:47 -05:00
|
|
|
method. That is, if the overridden method requires arguments, you must pass
|
|
|
|
them to @code{__super()}. This allows you to modify the argument list before
|
|
|
|
it is sent to the overridden method.
|
2011-03-21 19:39:59 -04:00
|
|
|
|
2014-07-28 00:00:58 -04:00
|
|
|
@subsubsection Arbitrary Supertype Method Invocation
|
|
|
|
The aforementioned @code{__super} method satisfies invoking an overridden
|
|
|
|
method within the context of the method that is overriding it, but falls
|
|
|
|
short when needing to invoke an overridden method outside of that context.
|
|
|
|
|
|
|
|
As an example, consider that @code{AngryDog} also implemented a
|
|
|
|
@code{pokeWithDeliciousBone} method, in which case we want to bypass the
|
|
|
|
dog's angry tendencies and fall back to behaving like a @code{LazyDog} (the
|
|
|
|
supertype). This poses a problem, as we have overridden @code{LazyDog#poke},
|
|
|
|
so calling @code{this.poke} would not yield the correct result (the dog
|
|
|
|
would still respond angerly). @code{__super} cannot be used, because that
|
|
|
|
would attempt to invoke a supermethod named
|
|
|
|
@code{pokeWithDeliciousBone}; no such method even exists, so in this case,
|
|
|
|
@code{__super} wouldn't even be defined.
|
|
|
|
|
|
|
|
We can remedy this using @code{this.poke.super}, which is a strict reference
|
|
|
|
to the overridden @code{poke} method (in this case, @code{LazyDog.poke}):
|
|
|
|
|
|
|
|
@float Figure, f:arbitrary-super-method
|
|
|
|
@verbatim
|
|
|
|
var AngryDog = Class( 'AngryDog' ).extend( LazyDog,
|
|
|
|
{
|
|
|
|
'public poke': function()
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
},
|
|
|
|
|
|
|
|
'public pokeWithDeliciousBone': function()
|
|
|
|
{
|
|
|
|
// invoke LazyDog.poke
|
|
|
|
this.poke.super.call( this );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// poke a new AngryDog instance with a delicious bone
|
|
|
|
AngryDog().pokeWithDeliciousBone();
|
|
|
|
|
|
|
|
// Output:
|
|
|
|
// Woof!
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using the method-supecific @code{super} reference}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
It is important to note that, in its current implementation, since
|
|
|
|
@code{super} is a reference to a function, its context must be provided
|
|
|
|
using the ECMAScript-native @code{apply} or @code{call} (the first argument
|
|
|
|
being the context); using @code{this} as the context (as shown above) will
|
|
|
|
invoke the method within the context of the calling
|
|
|
|
instance.@footnote{Specifically, it will invoke the method within the
|
|
|
|
context of the calling instance's private visibility object (@pxref{The
|
|
|
|
Visibility Object}). While this may seem like a bad idea---since it appears
|
|
|
|
to give the supermethod access to our private state---note that the method
|
|
|
|
wrapper for the overridden method will properly restore the private state of
|
|
|
|
the @emph{supertype} upon invocation.}
|
|
|
|
|
|
|
|
|
2011-03-21 00:04:44 -04:00
|
|
|
@node Type Checks and Polymorphism
|
|
|
|
@subsection Type Checks and Polymorphism
|
2014-01-17 22:27:47 -05:00
|
|
|
The fact that the API of the parent is inherited is a very important detail.
|
|
|
|
If the API of subtypes is guaranteed to be @emph{at least} that of the
|
|
|
|
parent, then this means that a function expecting a certain type can also
|
|
|
|
work with any subtypes. This concept is referred to as @dfn{polymorphism},
|
|
|
|
and is a very powerful aspect of Object-Oriented programming.
|
|
|
|
|
|
|
|
Let's consider a dog trainer. A dog trainer can generally train any type of
|
|
|
|
dog (technicalities aside), so it would stand to reason that we would want
|
|
|
|
our dog trainer to be able to train @var{LazyDog}, @var{AngryDog},
|
|
|
|
@var{TwoLeggedDog}, or any other type of @var{Dog} that we may throw at
|
|
|
|
him/her.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
|
|
|
@float Figure, f:polymorphism-uml
|
|
|
|
@image{img/composition-uml}
|
|
|
|
@caption{Class structure to demonstrate polymorphism}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Type checks are traditionally performed in JavaScript using the
|
|
|
|
@code{instanceOf} operator. While this can be used in most inheritance cases
|
2014-01-17 22:27:47 -05:00
|
|
|
with ease.js, it is not recommended. Rather, you are encouraged to use
|
|
|
|
ease.js's own methods for determining instance type@footnote{The reason for
|
|
|
|
this will become clear in future chapters. ease.js's own methods permit
|
|
|
|
checking for additional types, such as Interfaces.}. Support for the
|
|
|
|
@code{instanceOf} operator is not guaranteed.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
|
|
|
Instead, you have two choices with ease.js:
|
|
|
|
|
|
|
|
@table @code
|
|
|
|
@item Class.isInstanceOf( type, instance );
|
2014-01-17 22:27:47 -05:00
|
|
|
Returns @code{true} if @var{instance} is of type @var{type}. Otherwise,
|
|
|
|
returns @code{false}.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
|
|
|
@item Class.isA( type, instance );
|
|
|
|
Alias for @code{Class.isInstanceOf()}. Permits code that may read better
|
|
|
|
depending on circumstance and helps to convey the ``is a'' relationship that
|
|
|
|
inheritance creates.
|
|
|
|
@end table
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
@float Figure, f:instanceof-ex
|
|
|
|
@verbatim
|
|
|
|
var dog = Dog()
|
|
|
|
lazy = LazyDog(),
|
|
|
|
angry = AngryDog();
|
|
|
|
|
|
|
|
Class.isInstanceOf( Dog, dog ); // true
|
|
|
|
Class.isA( Dog, dog ); // true
|
|
|
|
Class.isA( LazyDog, dog ); // false
|
|
|
|
Class.isA( Dog, lazy ); // true
|
|
|
|
Class.isA( Dog, angry ); // true
|
|
|
|
|
|
|
|
// we must check an instance
|
|
|
|
Class.isA( Dog, LazyDog ); // false; instance expected, class given
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using ease.js to determine instance type}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
It is important to note that, as demonstrated in @ref{f:instanceof-ex}
|
|
|
|
above, an @emph{instance} must be passed as a second argument, not a class.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Using this method, we can ensure that the @var{DogTrainer} may only be used
|
|
|
|
with an instance of @var{Dog}. It doesn't matter what instance of @var{Dog}
|
|
|
|
- be it a @var{LazyDog} or otherwise. All that matters is that we are given
|
|
|
|
a @var{Dog}.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
|
|
|
@float Figure, f:polymorphism-easejs
|
|
|
|
@verbatim
|
|
|
|
var DogTrainer = Class( 'DogTrainer',
|
|
|
|
{
|
|
|
|
'public __construct': function( dog )
|
|
|
|
{
|
|
|
|
// ensure that we are given an instance of Dog
|
|
|
|
if ( Class.isA( Dog, dog ) === false )
|
|
|
|
{
|
2011-03-21 22:19:03 -04:00
|
|
|
throw TypeError( "Expected instance of Dog" );
|
2011-03-21 00:04:44 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
// these are all fine
|
|
|
|
DogTrainer( Dog() );
|
|
|
|
DogTrainer( LazyDog() );
|
|
|
|
DogTrainer( AngryDog() );
|
|
|
|
DogTrainer( TwoLeggedDog() );
|
|
|
|
|
|
|
|
// this is not fine; we're passing the class itself
|
|
|
|
DogTrainer( LazyDog );
|
|
|
|
|
|
|
|
// nor is this fine, as it is not a dog
|
|
|
|
DogTrainer( {} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Polymorphism in ease.js}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
It is very important that you use @emph{only} the API of the type that you
|
|
|
|
are expecting. For example, only @var{LazyDog} and @var{AngryDog} implement
|
|
|
|
a @code{poke()} method. It is @emph{not} a part of @var{Dog}'s API.
|
|
|
|
Therefore, it should not be used in the @var{DogTrainer} class. Instead, if
|
|
|
|
you wished to use the @code{poke()} method, you should require that an
|
|
|
|
instance of @var{LazyDog} be passed in, which would also permit
|
|
|
|
@var{AngryDog} (since it is a subtype of @var{LazyDog}).
|
2011-03-21 00:04:44 -04:00
|
|
|
|
|
|
|
Currently, it is necessary to perform this type check yourself. In future
|
2014-01-17 22:27:47 -05:00
|
|
|
versions, ease.js will allow for argument type hinting/strict typing, which
|
|
|
|
will automate this check for you.
|
2011-03-21 00:04:44 -04:00
|
|
|
|
2011-03-16 22:53:32 -04:00
|
|
|
@node Visibility Escalation
|
|
|
|
@subsection Visibility Escalation
|
2011-11-15 23:40:38 -05:00
|
|
|
Let @var{a\_n} denote a numeric level of visibility for @var{dfn\_n\^C} such
|
|
|
|
that the access modifiers (@pxref{Access Modifiers}) @code{private},
|
|
|
|
@code{protected} and @code{public} are associated with the values @code{1},
|
2014-01-17 22:27:47 -05:00
|
|
|
@code{2} and @code{3} respectively. Let @var{a'} represent @var{a} in
|
|
|
|
regards to @var{C'} (@pxref{Inheritance}).
|
2011-11-15 23:40:38 -05:00
|
|
|
|
|
|
|
For any member @var{n} of @var{dfn}, the following must be true:
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
@var{a'\_n} >= @var{a\_n}.
|
|
|
|
@item
|
|
|
|
@var{dfn\_n\^C'} cannot be redeclared without providing a new definition
|
|
|
|
(@var{value}).
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
@subsubsection Discussion
|
2014-01-17 22:27:47 -05:00
|
|
|
@dfn{Visibility escalation} is the act of increasing the visibility of a
|
|
|
|
member. Since private members cannot be inherited, this would then imply
|
|
|
|
that the only act to be considered "escallation" would be increasing the
|
|
|
|
level of visibility from @code{protected} to @code{private}.
|
|
|
|
|
|
|
|
Many follow the convention of prefixing private members with an underscore
|
|
|
|
but leaving omitting such a prefix from protected members. This is to permit
|
|
|
|
visibility escalation without renaming the member. Alternatively, a new
|
|
|
|
member can be defined without the prefix that will simply call the
|
|
|
|
overridden member (although this would then not be considered an escalation,
|
|
|
|
since the member name varies).
|
|
|
|
|
|
|
|
In order to increase the visibility, you must override the member; you
|
|
|
|
cannot simply redeclare it, leaving the parent definition in tact. For
|
|
|
|
properties, this has no discernible effect unless the @var{value} changes,
|
|
|
|
as you are simply redefining it. For methods, this means that you are
|
|
|
|
overriding the entire @var{value}. Therefore, you will either have to
|
|
|
|
provide an alternate implementation or call @samp{this.__super()} to invoke
|
|
|
|
the original method.
|
2011-11-15 23:40:38 -05:00
|
|
|
|
|
|
|
Note that @emph{you cannot de-escalate from public to protected}; this will
|
2014-01-17 22:27:47 -05:00
|
|
|
result in an error. This ensures that once a class defines an API,
|
|
|
|
subclasses cannot alter it. That API must be forever for all subtypes to
|
|
|
|
ensure that it remains polymorphic.
|
2011-03-16 22:53:32 -04:00
|
|
|
|
|
|
|
Let's take a look at an example.
|
|
|
|
|
|
|
|
@float Figure, f:vis-esc
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class(
|
|
|
|
{
|
2013-12-25 02:04:30 -05:00
|
|
|
'virtual protected canEscalate': 'baz',
|
2011-03-16 22:53:32 -04:00
|
|
|
|
2013-12-25 02:04:30 -05:00
|
|
|
'virtual protected escalateMe': function( arg )
|
2011-03-16 22:53:32 -04:00
|
|
|
{
|
|
|
|
console.log( 'In escalateMe' );
|
|
|
|
},
|
|
|
|
|
2013-12-25 02:04:30 -05:00
|
|
|
'virtual public cannotMakeProtected': function()
|
2011-03-16 22:53:32 -04:00
|
|
|
{
|
|
|
|
}
|
|
|
|
} ),
|
|
|
|
|
|
|
|
SubFoo = Foo.extend(
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Escalating a property means redefining it
|
|
|
|
*/
|
|
|
|
'public canEscalate': 'baz',
|
|
|
|
|
|
|
|
/**
|
|
|
|
* We can go protected -> public
|
|
|
|
*/
|
|
|
|
'public escalateMe': function( arg )
|
|
|
|
{
|
|
|
|
// simply call the parent method
|
|
|
|
this.__super( arg );
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Visibility can be escalated}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Note that, in the above example, making the public @var{cannotMakeProtected}
|
|
|
|
method protected would throw an error.
|
|
|
|
|
2016-07-15 00:15:11 -04:00
|
|
|
|
|
|
|
@node Error Subtypes
|
|
|
|
@subsection Error Subtypes
|
|
|
|
Extending ECMAScript's built-in @var{Error} type is a bit cumbersome (to
|
|
|
|
say the least)---it involves not only the traditional prototype chain,
|
|
|
|
but also setting specific properties within the constructor. Further,
|
|
|
|
different environments support different features (e.g. stack traces and
|
|
|
|
column numbers), and values are relative to the stack frame of the
|
|
|
|
@var{Error} subtype constructor itself.
|
|
|
|
|
|
|
|
With GNU ease.js, error subtyping is transparent:
|
|
|
|
|
|
|
|
@float Figure, f:error-extend
|
|
|
|
@verbatim
|
|
|
|
var MyError = Class( 'MyError' )
|
|
|
|
.extend( Error, {} );
|
|
|
|
|
|
|
|
var e = MyError( 'Foo' );
|
|
|
|
e.message; // Foo
|
|
|
|
e.name; // MyError
|
|
|
|
|
|
|
|
// -- if supported by environment --
|
|
|
|
e.stack; // stack beginning at caller
|
|
|
|
e.fileName; // caller filename
|
|
|
|
e.lineNumber; // caller line number
|
|
|
|
e.columnNumber; // caller column number
|
|
|
|
|
|
|
|
// general case
|
|
|
|
throw MyError( 'Foo' );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Transparent @var{Error} extending in ease.js}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
If ease.js detects that you are extending an @var{Error} object or any
|
|
|
|
of its subtypes, it will handle a number of things for you, depending on
|
|
|
|
environment:
|
|
|
|
|
|
|
|
@enumerate
|
|
|
|
@item Produce a default constructor method (@pxref{Constructors}) that
|
|
|
|
assigns the error message to the string passed as the first argument;
|
|
|
|
|
|
|
|
@item Sets the error name to the class name;
|
|
|
|
|
|
|
|
@item Provides a stack trace via @var{stack}, if supported by the
|
|
|
|
environment, stripping itself from the head of the stack; and
|
|
|
|
|
|
|
|
@item Sets any of @var{fileName}, @var{lineNumber}, and/or
|
|
|
|
@var{columnNumber} when supported by the environment.
|
|
|
|
@end enumerate
|
|
|
|
|
|
|
|
If a constructor method is provided in the class definition
|
|
|
|
(@pxref{Constructors}), then it will be invoked immediately after the
|
|
|
|
error object is initialized by the aforementioned default
|
|
|
|
constructor.@footnote{The reason that ease.js
|
|
|
|
does not permit overriding the generated constructor is an
|
|
|
|
implementation detail: the generated constructor is not on the
|
|
|
|
supertype, so there is not anything to actually override. Further, the
|
|
|
|
generated constructor provides a sane default behavior that should be
|
|
|
|
implicit in error classes anyway; that behavior can be overridden simply
|
|
|
|
be re-assigning the values that are assigned for you (e.g. name or line
|
|
|
|
number).} @var{this.__super} in that context refers to the constructor
|
|
|
|
of the supertype (as would be expected), @emph{not} the default error
|
|
|
|
constructor.
|
|
|
|
|
|
|
|
ease.js will automatically detect what features are supported by the
|
|
|
|
current environment, and will @emph{only} set respective values if the
|
|
|
|
environment itself would normally set them. For example, if ease.js can
|
|
|
|
determine a column number from the stack trace, but the environment does
|
|
|
|
not normally set @var{columnNumber} on @var{Error} objects, then neither
|
|
|
|
will ease.js; this leads to predictable and consistent behavior.
|
|
|
|
|
|
|
|
ease.js makes its best attempt to strip itself from the head of the
|
|
|
|
stack trace. To see why this is important, consider the generally
|
|
|
|
recommended way of creating an @var{Error} subtype in ECMAScript:
|
|
|
|
|
|
|
|
@float Figure, f:ecma-error-extend
|
|
|
|
@verbatim
|
|
|
|
function ErrorSubtype( message )
|
|
|
|
{
|
|
|
|
var err = new Error();
|
|
|
|
|
|
|
|
this.name = 'ErrorSubtype';
|
|
|
|
this.message = message || 'Error';
|
|
|
|
this.stack = err.stack;
|
|
|
|
this.lineNumber = err.lineNumber;
|
|
|
|
this.columnNumber = err.columnNumber;
|
|
|
|
this.fileName = err.fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
ErrorSubtype.prototype = new Error();
|
|
|
|
ErrorSubtype.prototype.constructor = ErrorSubtype;
|
|
|
|
@end verbatim
|
|
|
|
@caption{@var{Error} subtyping in plain ECMAScript 3}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Not only is @ref{f:ecma-error-extend} all boilerplate and messy, but
|
|
|
|
it's not entirely truthful: To get a stack trace, @var{Error} is
|
|
|
|
instantiated within the constructor @var{ErrorSubtype}; this ensures
|
|
|
|
that the stack trace will actually include the caller. Unfortunately,
|
|
|
|
it also includes the @emph{current frame}; the topmost frame in the
|
|
|
|
stack trace will be @var{ErrorSubtype} itself. To make matters worse,
|
|
|
|
all of @var{lineNumber}, @var{columNumber}, and @var{fileName} (if
|
|
|
|
defined) will be set to the stack frame of our constructor, @emph{not}
|
|
|
|
the caller.
|
|
|
|
|
|
|
|
ease.js will set each of those values to represent the caller. To do
|
|
|
|
so, it parses common stack trace formats. Should it fail, it simply
|
|
|
|
falls back to the default behavior of including itself in the stack
|
|
|
|
frame.
|
|
|
|
|
|
|
|
The end result of all of this is---hopefully---concise @var{Error}
|
|
|
|
subtypes that actually function as you would expect of an @var{Error},
|
|
|
|
without any boilerplate at all. The @var{Error} subtypes created with
|
|
|
|
ease.js can be extended like the built-ins, and may extend any of the
|
|
|
|
built-in error types (e.g. @var{TypeError} and @var{SyntaxError}).
|
|
|
|
|
|
|
|
|
2011-11-27 22:53:43 -05:00
|
|
|
@node Final Classes
|
|
|
|
@subsection Final Classes
|
|
|
|
@table @code
|
|
|
|
@item F = FinalClass( string @var{name}, Object @var{dfn} )
|
|
|
|
Define final named class @var{C} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
|
|
|
|
|
|
|
@item F = FinalClass( string @var{name} ).extend( Object @var{dfn} )
|
|
|
|
Define final named class @var{C} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
|
|
|
|
|
|
|
@item F = FinalClass( Object @var{dfn} )
|
|
|
|
Define anonymous final class @var{C} as described by @var{dfn}.
|
|
|
|
|
|
|
|
@item F = FinalClass.extend( Object @var{dfn } )
|
|
|
|
Define anonymous final class @var{C} as described by @var{dfn}.
|
|
|
|
@end table
|
|
|
|
|
|
|
|
Final classes operate exactly as ``normal'' classes do (@pxref{Defining
|
|
|
|
Classes}), with the exception that they cannot be inherited from.
|
|
|
|
|
2011-05-15 08:50:30 -04:00
|
|
|
|
|
|
|
@node Static Members
|
|
|
|
@section Static Members
|
|
|
|
@dfn{Static members} do not require instantiation of the containing class in
|
2014-01-17 22:27:47 -05:00
|
|
|
order to be used, but may also be called by instances. They are attached to
|
|
|
|
the class itself rather than an instance. Static members provide convenience
|
|
|
|
under certain circumstances where class instantiation is unnecessary and
|
|
|
|
permit sharing data between instances of a class. However, static members,
|
|
|
|
when used improperly, can produce poorly designed classes and tightly
|
|
|
|
coupled code that is also difficult to test. Static properties also
|
|
|
|
introduce problems very similar to global variables.
|
|
|
|
|
|
|
|
Let us consider an implementation of the factory pattern. Class
|
|
|
|
@var{BigBang} will declare two static methods in order to satisfy different
|
|
|
|
means of instantiation: @code{fromBraneCollision()} and
|
|
|
|
@code{fromBigCrunch()} (for the sake of the example, we're not going to
|
|
|
|
address every theory). Let us also consider that we want to keep track of
|
|
|
|
the number of big bangs in our universe (perhaps to study whether or not a
|
|
|
|
"Big Crunch" could have potentially happened in the past) by incrementing a
|
|
|
|
counter each time a new big bang occurs. Because we are using a static
|
|
|
|
method, we cannot use a property of an instance in order to store this data.
|
|
|
|
Therefore, we will use a static property of class @var{BigBang}.
|
2011-05-15 08:50:30 -04:00
|
|
|
|
|
|
|
@float Figure, f:static-ex
|
|
|
|
@verbatim
|
|
|
|
var BigBang = Class( 'BigBang',
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Number of big bangs that has occurred
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
'private static _count': 0,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* String representing the type of big bang
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
'private _type': '',
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new big bang from the collision of two membranes
|
|
|
|
*
|
|
|
|
* @return {BraneSet} the set of branes that collided
|
|
|
|
*
|
|
|
|
* @return {BigBang} new big bang
|
|
|
|
*/
|
|
|
|
'public static fromBraneCollision': function( brane_set )
|
|
|
|
{
|
|
|
|
// do initialization tasks...
|
|
|
|
|
|
|
|
return BigBang( 'brane', brane_set.getData() );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new big bang following a "Big Crunch"
|
|
|
|
*
|
|
|
|
* @param {BigCrunch} prior crunch
|
|
|
|
*
|
|
|
|
* @return {BigBang} new big bang
|
|
|
|
*/
|
|
|
|
'public static fromBigCrunch': function( crunch )
|
|
|
|
{
|
|
|
|
// do initialization tasks...
|
|
|
|
|
|
|
|
return BigBang( 'crunch', crunch.getData() );
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the total number of big bangs that have occurred
|
|
|
|
*
|
|
|
|
* @return {number} total number of big bangs
|
|
|
|
*/
|
|
|
|
'public static getTotalCount': function()
|
|
|
|
{
|
|
|
|
return this.$('_count');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a new big bang
|
|
|
|
*
|
|
|
|
* @param {string} type big bang type
|
|
|
|
* @param {object} data initialization data
|
|
|
|
*
|
|
|
|
* @return {undefined}
|
|
|
|
*/
|
2015-09-16 00:38:04 -04:00
|
|
|
__construct: function( type, data )
|
2011-05-15 08:50:30 -04:00
|
|
|
{
|
|
|
|
this._type = type;
|
|
|
|
|
|
|
|
// do complicated stuff with data
|
|
|
|
|
|
|
|
// increment big bang count
|
|
|
|
this.__self.$( '_count',
|
|
|
|
this.__self.$('count') + 1
|
|
|
|
);
|
|
|
|
},
|
|
|
|
} );
|
|
|
|
|
|
|
|
// create one of each
|
|
|
|
var brane_bang = BigBang.fromBraneCollision( branes ),
|
|
|
|
crunch_bang = BigBang.fromBigCrunch( crunch_incident );
|
|
|
|
|
|
|
|
console.log( "Total number of big bangs: %d", BigBang.getTotalCount() );
|
|
|
|
// Total number of big bangs: 2
|
|
|
|
@end verbatim
|
|
|
|
@caption{Static member example using the factory pattern}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Due to limitations of pre-ECMAScript 5 implementations, ease.js's static
|
2014-01-17 22:27:47 -05:00
|
|
|
implementation must be broken into two separate parts: properties and
|
|
|
|
methods.
|
2011-05-15 08:50:30 -04:00
|
|
|
|
|
|
|
@menu
|
|
|
|
* Static Methods::
|
2011-05-15 09:18:10 -04:00
|
|
|
* Static Properties::
|
2011-05-30 11:16:45 -04:00
|
|
|
* Constants:: Immutable static properties
|
2011-05-15 08:50:30 -04:00
|
|
|
@end menu
|
|
|
|
|
|
|
|
@node Static Methods
|
|
|
|
@subsection Static Methods
|
2014-01-17 22:27:47 -05:00
|
|
|
In @ref{f:static-ex}, we implemented three static methods: two factory
|
|
|
|
methods, @code{fromBraneCollision()} and @code{FromBigCrunch()}, and one
|
|
|
|
getter method to retrieve the total number of big bangs,
|
|
|
|
@code{getTotalCount()}. These methods are very similar to instance methods
|
|
|
|
we are already used to, with a few important differences:
|
2011-05-15 09:18:10 -04:00
|
|
|
|
|
|
|
@enumerate
|
|
|
|
@item
|
|
|
|
Static methods are declared with the @code{static} keyword.
|
|
|
|
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
In the body, @code{this} is bound to the class itself, rather than the
|
|
|
|
instance.
|
2011-05-15 09:18:10 -04:00
|
|
|
|
|
|
|
@item
|
|
|
|
Static methods cannot call any non-static methods of the same class without
|
|
|
|
first instantiating it.
|
|
|
|
@end enumerate
|
|
|
|
|
|
|
|
The final rule above is not true when the situation is reversed. Non-static
|
2014-01-17 22:27:47 -05:00
|
|
|
methods @emph{can} call static methods through use of the @var{__self}
|
|
|
|
object, which is a reference to the class itself. That is, @var{this} in a
|
|
|
|
static method is the same object as @var{this.__self} in a non-static
|
|
|
|
method. This is demonstrated by @code{getTotalCount()}
|
2011-05-15 09:18:10 -04:00
|
|
|
|
|
|
|
@verbatim
|
|
|
|
this.$('_count')
|
|
|
|
@end verbatim
|
|
|
|
|
|
|
|
and @code{__construct()}.
|
|
|
|
|
|
|
|
@verbatim
|
|
|
|
this.__self.$('_count')
|
|
|
|
@end verbatim
|
|
|
|
|
|
|
|
To help remember @var{__self}, consider what the name states. A class is a
|
2014-01-17 22:27:47 -05:00
|
|
|
definition used to create an object. The body of a method is a definition,
|
|
|
|
which is defined on the class. Therefore, even though the body of a method
|
|
|
|
may be called in the context of an instance, it is still part of the class.
|
|
|
|
As such, @var{__self} refers to the class.
|
2011-05-15 09:18:10 -04:00
|
|
|
|
|
|
|
@node Static Properties
|
|
|
|
@subsection Static Properties
|
2011-05-15 09:38:24 -04:00
|
|
|
You have likely noticed by now that static properties are handled a bit
|
2014-01-17 22:27:47 -05:00
|
|
|
differently than both static methods and non-static properties. This
|
|
|
|
difference is due to pre-ECMAScript 5 limitations and is discussed at length
|
|
|
|
in the @ref{Static Implementation} section.
|
2011-05-15 09:38:24 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Static properties are read from and written to using the @dfn{static
|
|
|
|
accessor method} @code{$()}. This method name was chosen because the
|
|
|
|
@code{$} prefix is common in scripting languages such as BASH, Perl (for
|
|
|
|
scalars) and PHP. The accessor method accepts two arguments, the second
|
|
|
|
being optional. If only the first argument is provided, the accessor method
|
|
|
|
acts as a getter, as in @ref{f:static-ex}'s @code{getTotalCount()}:
|
2011-05-15 09:38:24 -04:00
|
|
|
|
|
|
|
@verbatim
|
|
|
|
return this.$('_count');
|
|
|
|
@end verbatim
|
|
|
|
|
|
|
|
If the second argument is provided, it acts as a setter, as in
|
|
|
|
@code{__construct()}:
|
|
|
|
|
|
|
|
@verbatim
|
|
|
|
this.__self.$( '_count',
|
|
|
|
this.__self.$('count') + 1
|
|
|
|
);
|
|
|
|
@end verbatim
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Setting @code{undefined} values is supported. The @code{delete} operator is
|
|
|
|
not supported, as its use is both restricted by the language itself and
|
|
|
|
doesn't make sense to use in this context. As hinted by the example above,
|
|
|
|
the increment and decrement operators (@code{++} and @code{--}) are not
|
|
|
|
supported because JavaScript does not permit returning values by reference.
|
2011-05-15 09:38:24 -04:00
|
|
|
|
|
|
|
It is important to understand that, currently, the accessor method cannot be
|
|
|
|
omitted. Consider the following example:
|
|
|
|
|
|
|
|
@float Figure, f:static-accessor
|
|
|
|
@verbatim
|
|
|
|
var Foo = Class( 'Foo',
|
|
|
|
{
|
|
|
|
'public static bar': 'baz',
|
|
|
|
},
|
|
|
|
|
|
|
|
SubFoo = Class( 'SubFoo' ).extend( Foo, {} )
|
|
|
|
;
|
|
|
|
|
|
|
|
// correct
|
|
|
|
Foo.$( 'bar, 'baz2' );
|
|
|
|
Foo.$('bar'); // baz2
|
|
|
|
SubFoo.$('bar'); // baz2
|
|
|
|
SubFoo.$( 'bar', 'baz3' );
|
|
|
|
Foo.$('bar'); // baz3
|
|
|
|
|
|
|
|
// INCORRECT
|
|
|
|
Foo.bar = 'baz2';
|
|
|
|
Foo.bar; // baz2
|
|
|
|
SubFoo.bar; // undefined
|
|
|
|
@end verbatim
|
|
|
|
@caption{Static accessor method cannot be omitted}
|
|
|
|
@end float
|
2011-05-15 08:50:30 -04:00
|
|
|
|
2011-05-30 11:16:45 -04:00
|
|
|
@node Constants
|
|
|
|
@subsection Constants
|
|
|
|
@dfn{Constants}, in terms of classes, are immutable static properties. This
|
|
|
|
means that, once defined, a constant cannot be modified. Since the value is
|
2014-01-17 22:27:47 -05:00
|
|
|
immutable, it does not make sense to create instances of the property. As
|
|
|
|
such, constant values are implicitly static. This ensures that each
|
|
|
|
instance, as well 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{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 want an immutable value that shouldn't be exposed
|
|
|
|
to the rest of the world (e.g. a service URL, file path, etc). Consider the
|
|
|
|
following example in which we have a class responsible for reading mount
|
|
|
|
mounts from @file{/etc/fstab}:
|
2011-05-30 11:16:45 -04:00
|
|
|
|
|
|
|
@float Figure, f:const-ex
|
|
|
|
@verbatim
|
|
|
|
Class( 'MountPointIterator',
|
|
|
|
{
|
|
|
|
'private const _PATH': '/etc/fstab',
|
|
|
|
|
|
|
|
'private _mountPoints': [],
|
|
|
|
|
|
|
|
|
2015-09-16 00:38:04 -04:00
|
|
|
__construct: function()
|
2011-05-30 11:16:45 -04:00
|
|
|
{
|
|
|
|
var data = fs.readFileSync( this.$('_PATH') );
|
|
|
|
this._parseMountPoints( data );
|
|
|
|
},
|
|
|
|
|
|
|
|
// ...
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using the @code{const} keyword}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
In the above example, attempting to access the @var{_PATH} constant from
|
|
|
|
outside the class would return @code{undefined}. Had the constant been
|
|
|
|
declared as public, or had the visibility modifier omitted, it could have
|
|
|
|
been accessed just like any other static property:
|
2011-05-30 11:16:45 -04:00
|
|
|
|
|
|
|
@verbatim
|
|
|
|
// if PATH were a public constant value
|
|
|
|
MountPointIterator.$('PATH');
|
|
|
|
@end verbatim
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Any attempts to modify the value of a constant will result in an exception.
|
|
|
|
This will also work in pre-ES5 engines due to use of the @ref{Static
|
|
|
|
Properties, static accessor method} (@code{$()}).
|
2011-05-30 11:16:45 -04:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
It is important to note that constants prevent the @emph{value of the
|
|
|
|
property} from being reassigned. It @emph{does not} prevent modification of
|
|
|
|
the value that is @emph{referenced} by the property. For example, if we had
|
|
|
|
a constant @var{foo}, which references an object, such that
|
2011-05-30 11:16:45 -04:00
|
|
|
|
|
|
|
@verbatim
|
|
|
|
'const foo': { a: 'b' }
|
|
|
|
@end verbatim
|
|
|
|
|
|
|
|
it is perfectly legal to alter the object:
|
|
|
|
|
|
|
|
@verbatim
|
|
|
|
MyClass.$('foo').a = 'c';
|
|
|
|
@end verbatim
|
|
|
|
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@node Abstract Members
|
|
|
|
@section Abstract Members
|
|
|
|
@table @code
|
2011-12-21 20:13:42 -05:00
|
|
|
@item 'abstract [@var{keywords}] @var{name}': @var{params}
|
2014-01-17 22:27:47 -05:00
|
|
|
Declare an abstract method @var{name} as having @var{params} parameters,
|
|
|
|
having optional additional keywords
|
2011-11-20 00:24:53 -05:00
|
|
|
@var{keywords}.
|
|
|
|
@end table
|
2011-12-21 20:13:42 -05:00
|
|
|
Abstract members permit declaring an API, deferring the implementation to a
|
|
|
|
subtype. Abstract methods are declared as an array of string parameter names
|
|
|
|
@var{params}.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@verbatim
|
2014-01-17 22:27:47 -05:00
|
|
|
// declares abstract method 'connect' expecting the two parameters,
|
|
|
|
// 'host' and 'path'
|
2011-11-20 00:24:53 -05:00
|
|
|
{ 'abstract connect': [ 'host', 'path' ] }
|
|
|
|
@end verbatim
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
Abstract members are defined using the @ref{t:keywords,,@code{abstract}}
|
|
|
|
keyword.
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
Except in interfaces (@pxref{Interfaces}), where the
|
|
|
|
@ref{t:keywords,,@code{abstract}} keyword is implicit.
|
|
|
|
@end itemize
|
|
|
|
@item
|
|
|
|
Currently, only methods may be declared abstract.
|
|
|
|
@item
|
2011-12-21 20:13:42 -05:00
|
|
|
The subtype must implement at least the number of parameters declared in
|
|
|
|
@var{params}, but the names needn't match.
|
2011-11-20 00:24:53 -05:00
|
|
|
@itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
Each name in @var{params} must be a valid variable name, as satisfied by
|
|
|
|
the regular expression @code{/^[a-z_][a-z0-9_]*$/i}.
|
2011-12-20 23:56:46 -05:00
|
|
|
@item
|
2011-11-20 00:24:53 -05:00
|
|
|
The names are use purely for documentation and are not semantic.
|
|
|
|
@end itemize
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
Abstract members may only be a part of one of the following:
|
|
|
|
|
|
|
|
@menu
|
|
|
|
* Interfaces::
|
|
|
|
* Abstract Classes::
|
|
|
|
@end menu
|
|
|
|
|
|
|
|
@node Interfaces
|
|
|
|
@subsection Interfaces
|
|
|
|
@table @code
|
|
|
|
@item I = Interface( string @var{name}, Object @var{dfn} )
|
2014-01-17 22:27:47 -05:00
|
|
|
Define named interface @var{I} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@item I = Interface( string @var{name} ).extend( Object @var{dfn} )
|
2014-01-17 22:27:47 -05:00
|
|
|
Define named interface @var{I} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@item I = Interface( Object @var{dfn} )
|
|
|
|
Define anonymous interface @var{I} as described by @var{dfn}.
|
|
|
|
|
|
|
|
@item I = Interface.extend( Object @var{dfn } )
|
|
|
|
Define anonymous interface @var{I} as described by @var{dfn}.
|
|
|
|
@end table
|
|
|
|
|
|
|
|
Interfaces are defined with a syntax much like classes (@pxref{Defining
|
|
|
|
Classes}) with the following properties:
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
Interface @var{I} cannot be instantiated.
|
|
|
|
@item
|
|
|
|
Every member of @var{dfn} of @var{I} is implicitly
|
|
|
|
@ref{t:keywords,,@code{abstract}}.
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
Consequently, @var{dfn} of @var{I} may contain only abstract methods.
|
|
|
|
@end itemize
|
|
|
|
@item
|
|
|
|
Interfaces may only extend other interfaces (@pxref{Inheritance}).
|
|
|
|
@end itemize
|
|
|
|
|
2011-11-20 10:53:16 -05:00
|
|
|
@code{Interface} must be imported (@pxref{Including}) from
|
|
|
|
@code{easejs.Interface}; it is not available in the global scope.
|
|
|
|
|
2011-11-20 00:24:53 -05:00
|
|
|
@subsubsection Implementing Interfaces
|
|
|
|
@table @code
|
2014-01-17 22:27:47 -05:00
|
|
|
@item C = Class( @var{name} ).implement( @var{I\_0}[, ...@var{I\_n}]
|
|
|
|
).extend( @var{dfn} ) Define named class @var{C} identified by @var{name}
|
|
|
|
implementing all interfaces @var{I}, described by @var{dfn}.
|
2011-11-20 00:24:53 -05:00
|
|
|
@item C = Class.implement( @var{I\_0}[, ...@var{I\_n} ).extend( @var{dfn} )
|
2014-01-17 22:27:47 -05:00
|
|
|
Define anonymous class @var{C} implementing all interfaces @var{I},
|
|
|
|
described by @var{dfn}.
|
2011-11-20 00:24:53 -05:00
|
|
|
@end table
|
|
|
|
Any class @var{C} may implement any interface @var{I}, inheriting its API.
|
|
|
|
Unlike class inheritance, any class @var{C} may implement one or more
|
|
|
|
interfaces.
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
Class @var{C} implementing interfaces @var{I} will be considered a subtype
|
|
|
|
of every @var{I}.
|
2011-11-20 00:24:53 -05:00
|
|
|
@item
|
|
|
|
Class @var{C} must either:
|
|
|
|
@itemize
|
2014-01-17 22:27:47 -05:00
|
|
|
@item Provide a concrete definition for every member of @var{dfn} of
|
|
|
|
@var{I},
|
2011-11-20 00:24:53 -05:00
|
|
|
@item or be declared as an @code{AbstractClass} (@pxref{Abstract Classes})
|
|
|
|
@itemize
|
|
|
|
@item
|
2014-01-17 22:27:47 -05:00
|
|
|
@var{C} may be declared as an @code{AbstractClass} while still providing
|
|
|
|
a concrete definition for some of @var{dfn} of @var{I}.
|
2011-11-20 00:24:53 -05:00
|
|
|
@end itemize
|
|
|
|
@end itemize
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
@subsubsection Discussion
|
2014-01-17 22:27:47 -05:00
|
|
|
Consider a library that provides a websocket abstraction. Not all
|
|
|
|
environments support web sockets, so an implementation may need to fall back
|
|
|
|
on long polling via AJAX, Flash sockets, etc. If websocket support @emph{is}
|
|
|
|
available, one would want to use that. Furthermore, an environment may
|
|
|
|
provide its own type of socket that our library does not include support
|
|
|
|
for. Therefore, we would want to provide developers for that environment the
|
|
|
|
ability to define their own type of socket implementation to be used in our
|
|
|
|
library.
|
|
|
|
|
|
|
|
This type of abstraction can be solved simply by providing a generic API
|
|
|
|
that any operation on websockets may use. For example, this API may provide
|
|
|
|
@code{connect()}, @code{onReceive()} and @code{send()} operations, among
|
|
|
|
others. We could define this API in a @code{Socket} interface:
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@float Figure, f:interface-def
|
|
|
|
@verbatim
|
|
|
|
var Socket = Interface( 'Socket',
|
|
|
|
{
|
|
|
|
'public connect': [ 'host', 'port' ],
|
|
|
|
|
|
|
|
'public send': [ 'data' ],
|
|
|
|
|
|
|
|
'public onReceive': [ 'callback' ],
|
|
|
|
|
|
|
|
'public close': [],
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Defining an interface}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
We can then provide any number of @code{Socket} implementations:
|
|
|
|
|
|
|
|
@float Figure f:interface-impl
|
|
|
|
@verbatim
|
|
|
|
var WebSocket = Class( 'WebSocket' ).implement( Socket ).extend(
|
|
|
|
{
|
|
|
|
'public connect': function( host, port )
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
},
|
|
|
|
|
|
|
|
// ...
|
|
|
|
} ),
|
|
|
|
|
|
|
|
SomeCustomSocket = Class.implement( Socket ).extend(
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Implementing an interface}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Anything wishing to use sockets can work with this interface
|
|
|
|
polymorphically:
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@float Figure, f:interface-poly
|
|
|
|
@verbatim
|
|
|
|
var ChatClient = Class(
|
|
|
|
{
|
|
|
|
'private _socket': null,
|
|
|
|
|
|
|
|
__construct: function( socket )
|
|
|
|
{
|
|
|
|
// only allow sockets
|
|
|
|
if ( !( Class.isA( Socket, socket ) ) )
|
|
|
|
{
|
|
|
|
throw TypeError( 'Expected socket' );
|
|
|
|
}
|
|
|
|
|
|
|
|
this._socket = socket;
|
|
|
|
},
|
|
|
|
|
|
|
|
'public sendMessage': function( channel, message )
|
|
|
|
{
|
|
|
|
this._socket.send( {
|
|
|
|
channel: channel,
|
|
|
|
message: message,
|
|
|
|
} );
|
|
|
|
},
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Polymorphism with interfaces}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
We could now use @code{ChatClient} with any of our @code{Socket}
|
|
|
|
implementations:
|
|
|
|
|
|
|
|
@float Figure, f:interface-poly-use
|
|
|
|
@verbatim
|
|
|
|
ChatClient( WebSocket() ).sendMessage( '#lobby', "Sweet! WebSockets!" );
|
2014-01-17 22:27:47 -05:00
|
|
|
ChatClient( SomeCustomSocket() )
|
|
|
|
.sendMessage( '#lobby', "I can chat too!" );
|
2011-11-20 00:24:53 -05:00
|
|
|
@end verbatim
|
|
|
|
@caption{Obtaining flexibility via dependency injection}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
The use of the @code{Socket} interface allowed us to create a powerful
|
2014-01-17 22:27:47 -05:00
|
|
|
abstraction that will allow our library to work across any range of systems.
|
|
|
|
The use of an interface allows us to define a common API through which all
|
|
|
|
of our various components may interact without having to worry about the
|
|
|
|
implementation details - something we couldn't worry about even if we tried,
|
|
|
|
due to the fact that we want developers to support whatever environment they
|
|
|
|
are developing for.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
Let's make a further consideration. Above, we defined a @code{onReceive()}
|
2014-01-17 22:27:47 -05:00
|
|
|
method which accepts a callback to be called when data is received. What if
|
|
|
|
our library wished to use an @code{Event} interface as well, which would
|
|
|
|
allow us to do something like @samp{some_socket.on( 'receive', function()
|
|
|
|
@{@} )}?
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@float Figure, f:interface-impl-multi
|
|
|
|
@verbatim
|
|
|
|
var AnotherSocket = Class.implement( Socket, Event ).extend(
|
|
|
|
{
|
|
|
|
'public connect': // ...
|
|
|
|
|
|
|
|
'public on': // ... part of Event
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Implementing multiple interfaces}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Any class may implement any number of interfaces. In the above example,
|
2014-01-17 22:27:47 -05:00
|
|
|
@code{AnotherSocket} implemented both @code{Socket} and @code{Event},
|
|
|
|
allowing it to be used wherever either type is expected. Let's take a look:
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@float Figure, f:interface-multi-isa
|
|
|
|
@verbatim
|
|
|
|
Class.isA( Socket, AnotherSocket() ); // true
|
|
|
|
Class.isA( Event, AnotherSocket() ); // true
|
|
|
|
@end verbatim
|
2014-01-17 22:27:47 -05:00
|
|
|
@caption{Implementors of interfaces are considered subtypes of each
|
|
|
|
implemented interface}
|
2011-11-20 00:24:53 -05:00
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Interfaces do not suffer from the same problems as multiple inheritance,
|
|
|
|
because we are not providing any sort of implementation that may cause
|
|
|
|
conflicts.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
One might then ask - why interfaces instead of abstract classes
|
|
|
|
(@pxref{Abstract Classes})? Abstract classes require subclassing, which
|
|
|
|
tightly couples the subtype with its parent. One may also only inherit from
|
|
|
|
a single supertype (@pxref{Inheritance}), which may cause a problem in our
|
|
|
|
library if we used an abstract class for @code{Socket}, but a developer had
|
|
|
|
to inherit from another class and still have that subtype act as a
|
|
|
|
@code{Socket}.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
Interfaces have no such problem. Implementors are free to use interfaces
|
2014-01-17 22:27:47 -05:00
|
|
|
wherever they wish and use as many as they wish; they needn't worry that
|
|
|
|
they may be unable to use the interface due to inheritance or coupling
|
|
|
|
issues. However, although interfaces facilitate API reuse, they do not aid
|
|
|
|
in code reuse as abstract classes do@footnote{This is a problem that will
|
|
|
|
eventually be solved by the introduction of traits/mixins.}.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
|
|
|
@node Abstract Classes
|
|
|
|
@subsection Abstract Classes
|
2011-11-20 14:25:37 -05:00
|
|
|
@table @code
|
|
|
|
@item A = AbstractClass( string @var{name}, Object @var{dfn} )
|
|
|
|
Define named abstract class @var{A} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
|
|
|
|
|
|
|
@item A = AbstractClass( string @var{name} ).extend( Object @var{dfn} )
|
|
|
|
Define named abstract class @var{A} identified by @var{name} described by
|
|
|
|
@var{dfn}.
|
|
|
|
|
|
|
|
@item A = AbstractClass( Object @var{dfn} )
|
|
|
|
Define anonymous abstract class @var{A} as described by @var{dfn}.
|
|
|
|
|
|
|
|
@item A = AbstractClass.extend( Object @var{dfn } )
|
|
|
|
Define anonymous abstract class @var{A} as described by @var{dfn}.
|
|
|
|
@end table
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Abstract classes are defined with a syntax much like classes
|
|
|
|
(@pxref{Defining Classes}). They act just as classes do, except with the
|
|
|
|
following additional properties:
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item
|
|
|
|
Abstract class @var{A} cannot be instantiated.
|
|
|
|
@item
|
|
|
|
Abstract class @var{A} must contain at least one member of @var{dfn} that is
|
|
|
|
explicitly declared as @ref{t:keywords,,@code{abstract}}.
|
|
|
|
@item
|
|
|
|
Abstract classes may extend both concrete and abstract classes
|
|
|
|
@end itemize
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
An abstract class @emph{must} be used if any member of @var{dfn} is declared
|
|
|
|
as abstract. This serves as a form of self-documenting code, as it would
|
|
|
|
otherwise not be immediately clear whether or not a class was abstract (one
|
|
|
|
would have to look through every member of @var{dfn} to make that
|
|
|
|
determination).
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
@code{AbstractClass} must be imported (@pxref{Including}) from
|
|
|
|
@code{easejs.AbstractClass}; it is not available in the global scope.
|
|
|
|
|
|
|
|
@subsubsection Discussion
|
2014-01-17 22:27:47 -05:00
|
|
|
Abstract classes allow the partial implementation of an API, deferring
|
|
|
|
portions of the implementation to subtypes (@pxref{Inheritance}). As an
|
|
|
|
example, let's consider an implementation of the @dfn{Abstract Factory}
|
|
|
|
pattern@footnote{See Abstract Factory, GoF}) which is responsible for the
|
|
|
|
instantiation and initialization of an object without knowing its concrete
|
|
|
|
type.
|
|
|
|
|
|
|
|
Our hypothetical library will be a widget abstraction. For this example, let
|
|
|
|
us consider that we need a system that will work with any number of
|
|
|
|
frameworks, including jQuery UI, Dojo, YUI and others. A particular dialog
|
|
|
|
needs to render a simple @code{Button} widget so that the user may click
|
|
|
|
"OK" when they have finished reading. We cannot instantiate the widget from
|
|
|
|
within the dialog itself, as that would tightly couple the chosen widget
|
|
|
|
subsystem (jQuery UI, etc) to the dialog, preventing us from changing it in
|
|
|
|
the future. Alternatively, we could have something akin to a switch
|
|
|
|
statement in order to choose which type of widget to instantiate, but that
|
|
|
|
would drastically inflate maintenance costs should we ever need to add or
|
|
|
|
remove support for other widget system in the future.
|
|
|
|
|
|
|
|
We can solve this problem by allowing another object, a
|
|
|
|
@code{WidgetFactory}, to perform that instantiation for us. The dialog could
|
|
|
|
accept the factory in its constructor, like so:
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
@float Figure, f:abstract-factory-use
|
|
|
|
@verbatim
|
|
|
|
Class( 'Dialog',
|
|
|
|
{
|
|
|
|
'private _factory': null,
|
|
|
|
|
2011-11-20 18:08:52 -05:00
|
|
|
__construct: function( factory )
|
2011-11-20 14:25:37 -05:00
|
|
|
{
|
|
|
|
if ( !( Class.isA( WidgetFactory, factory ) ) )
|
|
|
|
{
|
|
|
|
throw TypeError( 'Expected WidgetFactory' );
|
|
|
|
}
|
|
|
|
|
|
|
|
this._factory = factory;
|
|
|
|
},
|
|
|
|
|
|
|
|
'public open': function()
|
|
|
|
{
|
|
|
|
// before we open the dialog, we need to create and add the widgets
|
|
|
|
var btn = this._factory.createButtonWidget( 'btn_ok', "OK" );
|
|
|
|
|
|
|
|
// ...
|
|
|
|
},
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Hypothetical use case for our Abstract Factory}
|
|
|
|
@end float
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
We now have some other important considerations. As was previously
|
|
|
|
mentioned, @code{Dialog} itself could have determined which widget to
|
|
|
|
instantiate. By using a factory instead, we are moving that logic to the
|
|
|
|
factory, but we are now presented with a similar issue. If we use something
|
|
|
|
like a switch statement to decide what class should be instantiated, we are
|
|
|
|
stuck with modifying the factory each and every time we add or remove
|
|
|
|
support for another widget library.
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
This is where an abstract class could be of some benefit. Let's consider the
|
2014-01-17 22:27:47 -05:00
|
|
|
above call to @code{createButtonWidget()}, which accepted two arguments: an
|
|
|
|
id for the generated DOM element and a label for the button. Clearly, there
|
|
|
|
is some common initialization logic that can occur between each of the
|
|
|
|
widgets. However, we do not want to muddy the factory up with log to
|
|
|
|
determine what widget can be instantiated. The solution is to define the
|
|
|
|
common logic, but defer the actual instantiation of the @code{Widget} to
|
|
|
|
subtypes:
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
@float Figure, f:abstract-factory-define
|
|
|
|
@verbatim
|
|
|
|
AbstractClass( 'WidgetFactory',
|
|
|
|
{
|
|
|
|
'public createButtonWidget': function( id, label )
|
|
|
|
{
|
2014-01-17 22:27:47 -05:00
|
|
|
// note that this is a call to an abstract method; the
|
|
|
|
// implementation is not yet defined
|
2011-11-20 14:25:37 -05:00
|
|
|
var widget = this.getNewButtonWidget();
|
|
|
|
|
|
|
|
// perform common initialization tasks
|
|
|
|
widget.setId( id );
|
|
|
|
widget.setLabel( label );
|
|
|
|
|
|
|
|
// return the completed widget
|
|
|
|
return widget;
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// declared with an empty array because it has no parameters
|
|
|
|
'abstract protected getNewButtonWidget': [],
|
|
|
|
} );
|
|
|
|
@end verbatim
|
|
|
|
@caption{Defining our Abstract Factory}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
As demonstrated in @ref{f:abstract-factory-define} above, we can see a very
|
2014-01-17 22:27:47 -05:00
|
|
|
interesting aspect of abstract classes: we are making a call to a method
|
|
|
|
that is not yet defined (@code{getNewButtonWidget()}@footnote{Note that we
|
|
|
|
declared this method as @ref{t:keywords,,@code{protected}} in order to
|
|
|
|
encapsulate which the widget creation logic (@pxref{Access Modifiers
|
|
|
|
Discussion}). Users of the class should not be concerned with how we
|
|
|
|
accomplish our job. Indeed, they should be concerned only with the fact that
|
|
|
|
we save them the trouble of determining which classes need to be
|
|
|
|
instantiated by providing them with a convenient API.}). Instead, by
|
|
|
|
declaring it @ref{t:keywords,,@code{abstract}}, we are stating that we want
|
|
|
|
to call this method, but it is up to a subtype to actually define it. It is
|
|
|
|
for this reason that abstract classes cannot be instantiated - they cannot
|
|
|
|
be used until each of the abstract methods have a defined implementation.
|
|
|
|
|
|
|
|
We can now define a concrete widget factory (@pxref{Inheritance}) for each
|
|
|
|
of the available widget libraries@footnote{Of course, the @code{Widget}
|
|
|
|
itself would be its own abstraction, which may be best accomplished by the
|
|
|
|
Adapter pattern.}:
|
2011-11-20 14:25:37 -05:00
|
|
|
|
|
|
|
@float Figure, f:concrete-abstract-factory
|
|
|
|
@verbatim
|
|
|
|
Class( 'JqueryUiWidgetFactory' )
|
|
|
|
.extend( WidgetFactory,
|
|
|
|
{
|
|
|
|
// concrete method
|
|
|
|
'protected getNewButtonWidget': function()
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
},
|
|
|
|
} );
|
|
|
|
|
|
|
|
Class( 'DojoWidgetFactory' )
|
|
|
|
.extend( WidgetFactory,
|
|
|
|
{
|
|
|
|
// ...
|
|
|
|
} );
|
|
|
|
|
|
|
|
// ...
|
|
|
|
@end verbatim
|
|
|
|
@caption{Defining our concrete factories}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
With that, we have solved our problem. Rather than using a simple switch
|
|
|
|
statement, we opted for a polymorphic solution:
|
|
|
|
|
|
|
|
@float Figure, f:abstract-factory-inject
|
|
|
|
@verbatim
|
2014-01-17 22:27:47 -05:00
|
|
|
// we can use whatever widget library we wish by injecting it into
|
|
|
|
// Dialog
|
2011-11-20 14:25:37 -05:00
|
|
|
Dialog( JqueryUiWidgetFactory() ).show();
|
|
|
|
Dialog( DojoWidgetFactory() ).show();
|
|
|
|
Dialog( YuiWidgetFactory() ).show();
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using our abstract factory @code{WidgetFactory} via dependency
|
|
|
|
injection}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Now, adding or removing libraries is as simple as defining or removing a
|
|
|
|
@code{WidgetFactory} class.
|
|
|
|
|
2014-01-17 22:27:47 -05:00
|
|
|
Another noteworthy mention is that this solution could have just as easily
|
|
|
|
used an interface instead of an abstract class (@pxref{Interfaces}). The
|
|
|
|
reason we opted for an abstract class in this scenario is due to code reuse
|
|
|
|
(the common initialization code), but in doing so, we have tightly coupled
|
|
|
|
each subtype with the supertype @code{WidgetFactory}. There are a number of
|
|
|
|
trade-offs with each implementation; choose the one that best fits your
|
|
|
|
particular problem.
|
2011-11-20 00:24:53 -05:00
|
|
|
|
2014-01-06 00:20:06 -05:00
|
|
|
|
|
|
|
@node Method Proxies
|
|
|
|
@section Method Proxies
|
|
|
|
@table @code
|
|
|
|
@item 'proxy [@var{keywords}] @var{name}': @var{destmember}
|
|
|
|
Declare a proxy method @var{name}, having optional additional keywords
|
|
|
|
@var{keywords}, that invokes a method of the same name on object
|
|
|
|
@var{destmember} and returns its result.
|
|
|
|
@end table
|
|
|
|
Method proxies help to eliminate boilerplate code for calling methods on an
|
|
|
|
encapsulated object---a task that is very common with proxy and decorator
|
|
|
|
design patterns.
|
|
|
|
|
|
|
|
@float Figure, f:method-proxy-use
|
|
|
|
@verbatim
|
|
|
|
var Pidgin = Class( 'Pidgin',
|
|
|
|
{
|
|
|
|
'private _name': 'Flutter',
|
|
|
|
|
|
|
|
'public cheep': function( chirp )
|
|
|
|
{
|
|
|
|
return this._name + ": cheep " + chirp;
|
|
|
|
}
|
|
|
|
|
|
|
|
'public rename': function( name )
|
|
|
|
{
|
|
|
|
this._name = ''+name;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
} );
|
|
|
|
|
|
|
|
var IratePidginCheep = Class( 'IratePidginCheep',
|
|
|
|
{
|
|
|
|
'private _pidgin': null,
|
|
|
|
|
|
|
|
__construct: function( pidgin )
|
|
|
|
{
|
|
|
|
this._pidgin = pidgin;
|
|
|
|
}
|
|
|
|
|
|
|
|
// add our own method
|
|
|
|
'public irateCheep': function( chirp )
|
|
|
|
{
|
|
|
|
return this._pidgin.cheep( chirp ).toUpperCase();
|
|
|
|
},
|
|
|
|
|
|
|
|
// retain original methods
|
|
|
|
'proxy cheep': '_pidgin',
|
|
|
|
'proxy rename': '_pidgin',
|
|
|
|
} );
|
|
|
|
|
|
|
|
var irate = IratePidginCheep( Pidgin() );
|
|
|
|
|
|
|
|
irate.cheep( 'chirp' );
|
|
|
|
// "Flutter: cheep chirp"
|
|
|
|
irate.setName( 'Butter' ).cheep( 'chirp' );
|
|
|
|
// "Butter: cheep chirp"
|
|
|
|
irate.irateCheep( 'chop' );
|
|
|
|
// "BUTTER: CHEEP CHOP"
|
|
|
|
@end verbatim
|
|
|
|
@caption{Using the @code{proxy} keyword to proxy @code{cheep} and
|
|
|
|
@code{rename} method calls to the object stored in property @code{_pidgin}.}
|
|
|
|
@end float
|
|
|
|
|
|
|
|
Consider some object @code{O} whoose class uses method proxies.
|
|
|
|
|
|
|
|
@itemize
|
|
|
|
@item All arguments of proxy method @code{O.name} are forwarded to
|
|
|
|
@code{destmember.name} untouched.
|
|
|
|
|
|
|
|
@item The return value provided by @code{destmember.name} is returned to
|
|
|
|
the caller of @code{O.name} untouched, except that
|
|
|
|
@itemize
|
|
|
|
@item If @code{destmember.name} returns @code{destmember} (that is,
|
|
|
|
returns @code{this}), it will be replaced with @code{O}; this ensures
|
|
|
|
that @code{destmember} remains encapsulated and preserves method
|
|
|
|
chaining.
|
|
|
|
@end itemize
|
|
|
|
|
|
|
|
@item If @code{destmember} is not an object, calls to @code{O.name} will
|
|
|
|
immediately fail in error.
|
|
|
|
|
|
|
|
@item If @code{destmember.name} is not a function, calls to @code{O.name}
|
|
|
|
will immediately fail in error.
|
|
|
|
|
|
|
|
@item @emph{N.B.: Static method proxies are not yet supported.}
|
|
|
|
@end itemize
|