[#5] Added interface documentation to manual (abstract class pending)
parent
b2fcf8880a
commit
199bba3c38
259
doc/classes.texi
259
doc/classes.texi
|
@ -72,6 +72,7 @@ ease.js, until such a point where prototypes are no longer adequate.
|
||||||
* Defining Classes:: Learn how to define a class with ease.js
|
* Defining Classes:: Learn how to define a class with ease.js
|
||||||
* Inheritance:: Extending classes from another
|
* Inheritance:: Extending classes from another
|
||||||
* Static Members:: Members whose use do not require instantiation
|
* Static Members:: Members whose use do not require instantiation
|
||||||
|
* Abstract Members:: Declare members, deferring their definition to subtypes
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
|
|
||||||
|
@ -1240,3 +1241,261 @@ it is perfectly legal to alter the object:
|
||||||
MyClass.$('foo').a = 'c';
|
MyClass.$('foo').a = 'c';
|
||||||
@end verbatim
|
@end verbatim
|
||||||
|
|
||||||
|
|
||||||
|
@node Abstract Members
|
||||||
|
@section Abstract Members
|
||||||
|
@table @code
|
||||||
|
@item 'abstract [@var{keywords}] @var{name}': @var{args}
|
||||||
|
Declare an abstract method @var{name} as having @var{args} arguments, having
|
||||||
|
optional additional keywords
|
||||||
|
@var{keywords}.
|
||||||
|
@end table
|
||||||
|
Abstract members permit defining an API, deferring the implementation to a
|
||||||
|
subtype. Abstract methods are declared as an array of string argument names
|
||||||
|
@var{args}.
|
||||||
|
|
||||||
|
@verbatim
|
||||||
|
// declares abstract method 'connect' expecting the two arguments, 'host'
|
||||||
|
// and 'path'
|
||||||
|
{ '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
|
||||||
|
The subtype must implement at least the number of arguments declared in
|
||||||
|
@var{args}, but the names needn't match.
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
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} )
|
||||||
|
Define named interface @var{I} identified by @var{name} described by @var{dfn}.
|
||||||
|
|
||||||
|
@item I = Interface( string @var{name} ).extend( Object @var{dfn} )
|
||||||
|
Define named interface @var{I} identified by @var{name} described by @var{dfn}.
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@subsubsection Implementing Interfaces
|
||||||
|
@table @code
|
||||||
|
@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}.
|
||||||
|
@item C = Class.implement( @var{I\_0}[, ...@var{I\_n} ).extend( @var{dfn} )
|
||||||
|
Define anonymous class @var{C} implementing all interfaces @var{I}, described
|
||||||
|
by @var{dfn}.
|
||||||
|
@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
|
||||||
|
Class @var{C} implementing interfaces @var{I} will be considered a subtype of
|
||||||
|
every @var{I}.
|
||||||
|
@item
|
||||||
|
Class @var{C} must either:
|
||||||
|
@itemize
|
||||||
|
@item Provide a concrete definition for every member of @var{dfn} of @var{I},
|
||||||
|
@item or be declared as an @code{AbstractClass} (@pxref{Abstract Classes})
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
@var{C} may be declared as an @code{AbstractClass} while still providing a
|
||||||
|
concrete definition for some of @var{dfn} of @var{I}.
|
||||||
|
@end itemize
|
||||||
|
@end itemize
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
@subsubsection Discussion
|
||||||
|
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:
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
Anything wishing to use sockets can work with this interface polymorphically:
|
||||||
|
|
||||||
|
@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!" );
|
||||||
|
ChatClient( SomeCustomSocket() ).sendMessage( '#lobby', "I can chat too!" );
|
||||||
|
@end verbatim
|
||||||
|
@caption{Obtaining flexibility via dependency injection}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
The use of the @code{Socket} interface allowed us to create a powerful
|
||||||
|
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.
|
||||||
|
|
||||||
|
Let's make a further consideration. Above, we defined a @code{onReceive()}
|
||||||
|
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() @{@} )}?
|
||||||
|
|
||||||
|
@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,
|
||||||
|
@code{AnotherSocket} implemented both @code{Socket} and @code{Event}, allowing
|
||||||
|
it to be used wherever either type is expected. Let's take a look:
|
||||||
|
|
||||||
|
@float Figure, f:interface-multi-isa
|
||||||
|
@verbatim
|
||||||
|
Class.isA( Socket, AnotherSocket() ); // true
|
||||||
|
Class.isA( Event, AnotherSocket() ); // true
|
||||||
|
@end verbatim
|
||||||
|
@caption{Implementors of interfaces are considered subtypes of each implemented
|
||||||
|
interface}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
Interfaces do not suffer from the same problems as multiple inheritance, because
|
||||||
|
we are not providing any sort of implementation that may cause conflicts.
|
||||||
|
|
||||||
|
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}.
|
||||||
|
|
||||||
|
Interfaces have no such problem. Implementors are free to use interfaces
|
||||||
|
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.
|
||||||
|
|
||||||
|
@node Abstract Classes
|
||||||
|
@subsection Abstract Classes
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue