coope/sec/encap-hacks.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}