132 lines
4.4 KiB
TeX
132 lines
4.4 KiB
TeX
|
\section{Encapsulating the Hacks}
|
||
|
Imagine jumping into a project in order to make a simple modification and then
|
||
|
seeing the code in \jsref{lst:prot-share}. This is a far cry from the simple
|
||
|
protected member declarations in traditional classical object-oriented
|
||
|
languages. In fact, there becomes a point where the hacks discussed in the
|
||
|
previous sections become unmaintainable messes that add a great deal of
|
||
|
boilerplate code with little use other than to distract from the actual
|
||
|
software itself.
|
||
|
|
||
|
However, we do not have to settle for those messy implementations. Indeed, we
|
||
|
can come up with some fairly elegant and concise solutions by encapsulating the
|
||
|
hacks we have discussed into a classical object-oriented framework, library or
|
||
|
simple helper functions. Let's not get ahead of ourselves too quickly; we will
|
||
|
start exploring basic helper functions before we deal with diving into a full,
|
||
|
reusable framework.
|
||
|
|
||
|
This section is intended for educational and experimental purposes. Before using
|
||
|
these examples to develop your own class system for ECMAScript, ensure that none
|
||
|
of the existing systems satisfy your needs; your effort is best suited toward
|
||
|
the advancement of existing projects than the segregation caused by the
|
||
|
introduction of additional, specialty frameworks.\footnote{That is not to
|
||
|
discourage experimentation. Indeed, one of the best, most exciting and fun ways
|
||
|
to learn about these concepts are to implement them yourself.} These are
|
||
|
discussed a bit later.
|
||
|
|
||
|
\subsection{Constructor/Prototype Factory}
|
||
|
\label{sec:ctor-factory}
|
||
|
Section~\ref{sec:extending} offered one solution to the problem of creating a an
|
||
|
extensible constructor, allowing it to be used both to instantiate new objects
|
||
|
and as a prototype. Unfortunately, as \jsref{lst:ctor-extend} demonstrated, the
|
||
|
solution adds a bit of noise to the definition that will also be duplicated for
|
||
|
each constructor. The section ended with the promise of a cleaner, reusable
|
||
|
implementation. Perhaps we can provide that.
|
||
|
|
||
|
Consider once again the issue at hand. The constructor, when called
|
||
|
conventionally with the \keyword{new} keyword to create a new instance, must
|
||
|
perform all of its construction logic. However, if we wish to use it as a
|
||
|
prototype, it is unlikely that we want to run \emph{any} of that logic --- we
|
||
|
are simply looking to have an object containing each of its members to use as a
|
||
|
prototype without the risk of modifying the prototype of the constructor in
|
||
|
question. Now consider how this issue is handled in other classical languages:
|
||
|
the \keyword{extend} keyword.
|
||
|
|
||
|
ECMAScript has no such keyword, so we will have to work on an implementation
|
||
|
ourselves. We cannot use the name \func{extend()}, as it is a reserved
|
||
|
name;\footnote{Perhaps for future versions of ECMAScript.} as such, we will
|
||
|
start with a simple \func{Class} factory function with which we can create new
|
||
|
``classes'' without supertypes. We can than provide a \func{Class.extend()}
|
||
|
method to define a ``class'' with a supertype.
|
||
|
|
||
|
\begin{lstlisting}[%
|
||
|
label=lst:ctor-factory,
|
||
|
caption=Constructor factory
|
||
|
]
|
||
|
var Class = ( function( extending )
|
||
|
{
|
||
|
var C = function( dfn )
|
||
|
{
|
||
|
// extend from an empty base
|
||
|
return C.extend( null, dfn );
|
||
|
};
|
||
|
|
||
|
C.extend = function( base, dfn )
|
||
|
{
|
||
|
base = base || function() {};
|
||
|
|
||
|
var ctor = function()
|
||
|
{
|
||
|
// do nothing if extending
|
||
|
if ( extending )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// call "actual" constructor
|
||
|
this.__construct &&
|
||
|
this.__construct.apply(
|
||
|
this, arguments
|
||
|
);
|
||
|
};
|
||
|
|
||
|
ctor.prototype = new base();
|
||
|
ctor.prototype.constructor = ctor;
|
||
|
|
||
|
copyTo( ctor.prototype, dfn );
|
||
|
|
||
|
return ctor;
|
||
|
};
|
||
|
|
||
|
function copyTo( dest, members )
|
||
|
{
|
||
|
var hasOwn = Object.prototype
|
||
|
.hasOwnProperty;
|
||
|
|
||
|
for ( var member in members )
|
||
|
{
|
||
|
if ( !hasOwn.call( members, member ) )
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
dest[ member ] = members[ member ];
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return C;
|
||
|
} )( false );
|
||
|
|
||
|
var Foo = Class(
|
||
|
{
|
||
|
__construct: function( name, ignore )
|
||
|
{
|
||
|
ignore || throw Error( "Ctor called" );
|
||
|
this._name = ''+( name );
|
||
|
},
|
||
|
|
||
|
getName: function()
|
||
|
{
|
||
|
return this._name;
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
var SubFoo = Class.extend( Foo,
|
||
|
{
|
||
|
setName: function( name )
|
||
|
{
|
||
|
this._name = ''+( name );
|
||
|
}
|
||
|
} );
|
||
|
|
||
|
\end{lstlisting}
|