Added remaining method content to "Private Member Encapsulation" section

- Private properties discussion will follow
master
Mike Gerwitz 2012-04-01 21:49:19 -04:00
parent 72b0bb1bee
commit 10095422dc
5 changed files with 178 additions and 4 deletions

View File

@ -0,0 +1,29 @@
var Database = Class(
{
// ...
forEachRow: function( callback )
{
for ( row in this._rows )
{
callback( row );
}
},
_getPassword: function()
{
return 'secret';
}
);
var db = Database( '...' ),
passwd = '';
// ...
db.forEachRow( function( row )
{
passwd = db.__priv._getPassword();
} );
// oops
unauthorizedDbOperation( 'user', passwd );

View File

@ -0,0 +1,13 @@
var Database = Class(
{
__construct: function( host, user, pass )
{
// __priv contains private members
var ip = this.__priv._resolveHost();
// __priv is undefined
this.__priv._connect( ip, user, pass );
},
// ...
} );

View File

@ -0,0 +1,19 @@
function wrap( method )
{
return function()
{
var prev = this.__priv;
// expose private member object
this.__priv =
this.constructor[ _privname ];
var retval = method.apply(
this, arguments
);
// restore previous value
this.__priv = prev;
return retval;
};
}

View File

@ -118,6 +118,6 @@ var Foo = Class(
} );
var inst = new Foo();
inst.getValue(); // "private"
inst._getPrivValue; // undefined
inst.__priv; // undefined
inst.getValue(); // "private"
inst._getPrivValue(); // TypeError
inst.__priv; // undefined

View File

@ -480,4 +480,117 @@ concept with the wrapper implementation, we arrive at
lastline=97
]{lst/ctor-factory-priv.js}
(INCOMPLETE.)
In order to expose the private methods \emph{only} from within wrapped methods,
\jsref{lst:ctor-factory-priv} relies on the fact that only the constructor
factory knows the name of the private ``prototype'' object (as denoted by
\var{\_privname}). Wrappers, before invoking the wrapped function (method), will
assign the private object to \code{this.\_priv} (line 84) and unassign it after
the wrapped function returns (line 91).\footnote{We set the value to
\keyword{undefined} rather than using the \operator{delete} operator because the
latter causes a slight performance hit under v8.} Methods may then access
private members by referencing \code{this.\_priv}.
% TODO: REFERENCE NEEDED FOR ABOVE V8 MENTION
\func{copyTo()}, now receiving both public and private destination objects as
arguments, will place all members prefixed with an underscore on the private
member object (lines 71--72). As has already been mentioned, the member will be
wrapped regardless of whether or not it is private (line 74), ensuring that
public methods also have access to private members.
\jsref{lst:ctor-factory-priv-ex} demonstrates how this implementation may be
used to define a private method \func{\_getPrivValue()} that is accessable only
to other methods; attempting to invoke the method publically would result in a
\code{TypeError} (resulting from an attempt to call \keyword{undefined} as if it
were a function). Also note that \var{Foo.\_\_priv}, although defined from
within the method \func{getValue()}, is \keyword{undefined} after the method
returns.
\lstinputlisting[%
label=lst:ctor-factory-priv-ex,
caption=Demonstrating use of private methods with constructor factory,
firstline=107
]{lst/ctor-factory-priv.js}
\subsubsection{Wrapper Implementation Concerns}
The wrapper implementation is not without its dilemmas. Firstly, consider how
\func{wrap()} clears \var{\_\_priv} in \jsref{lst:ctor-factory-priv} (lines
84--91). The wrapper requires that the call stack be cleared up to the point of
the invocation of the wrapped function. Consequently, this means that any code
executed before the call stack is cleared to that point will have access to the
instance during which time \code{this.\_priv} is assigned, giving that code
access to private members and breaking encapsulation.
\lstinputlisting[%
label=lst:ctor-factory-priv-exploit-stack,
caption=Exploiting wrapper implementation via callbacks to gain access to
private members outside of the class
]{lst/ctor-factory-priv-exploit.js}
This fatal flaw is demonstrated in \jsref{lst:ctor-factory-priv-exploit-stack},
which executes a callback before the wrapped function returns. That callback,
which has access to the instance that called it, is able to access the private
members because it is executed before its caller returns. There are three ways
to work around this:
\begin{enumerate}
\item Remove the assignment before invoking the callback,
\item Use \func{setTimeout()} or \func{setInterval()} to invoke the callback,
allowing the stack to clear before the callback is invoked, or
\item Do not invoke any functions that are not defined on the class itself.
\end{enumerate}
None of the above options are acceptable solutions. The first option adds
unnecessary logic to the method that makes assumptions about the underlying
system, which is especially dangerous if the implementation of \func{wrap()}
were to ever change. The second solution does not suffer from the same design
issues as the first, but forces the method to be asynchronous, which is not
always desirable. The third option is terribly prohibitive, as it not only
disallows any type of serial callback, but also disallows invoking any methods
of injected dependencies.
% TODO: Reference to section where this issue is highlighted, once available
A proper solution to this issue is obvious, but its discussion will be deferred
to future implementations due to additional complexities raised when dealing
with properties. Until that time, the reader should be aware of the issue and
consider potential solutions.
The second concern is a bit more subtle. Once again, we focus around lines
82--91 in \jsref{lst:ctor-factory-priv}. Consider what problems that this
wrapper may cause when dealing with nested method calls --- that is, one method
calling another on the same instance.
\lstinputlisting[%
label=lst:ctor-factory-priv-nested-clear,
caption=Problems with nested method calls given the \func{wrap()}
implementation in \jsref{lst:ctor-factory-priv}
]{lst/ctor-factory-priv-nested-clear.js}
This issues is demonstrated by \jsref{lst:ctor-factory-priv-nested-clear}. The
\var{Database} class's \func{\_\_construct()} method performs two private method
calls --- \func{\_resolveHost()}, to get the IP address of the host, and
\func{\_connect()}, which attempts to connect to the database. Unfortunately,
after the call to \func{\_resolveHost()}, its wrapper sets \var{\_\_priv} to
\keyword{undefined} (line 91 in \jsref{lst:ctor-factory-priv}), which will cause
the second method call to fail!
To resolve this issue, \func{wrap()} could store the previous value of
\code{this.\_\_priv} and then, instead of setting the value to
\keyword{undefined} after the wrapped function has returned, restore
\code{this.\_\_priv} to its original value. This modification is shown in
\jsref{lst:ctor-factory-priv-nested-fix}.
\lstinputlisting[%
label=lst:ctor-factory-priv-nested-fix,
caption=Fixing private object assignment in nested wrapped function calls by
restoring the previous value,
firstnumber=80
]{lst/ctor-factory-priv-nested-fix.js}
When the first wrapper is invoked, the previous value of \code{this.\_\_priv}
will be \keyword{undefined}, allowing the wrapper to continue to operate as it
used to. When nested wrappers are invoked, the previous value will contain the
private member object and will allow \code{this.\_\_priv} to be properly
restored on return. This functions much like a stack, using the call stack
instead of an array.\footnote{This solution could have easily been worked into
\jsref{lst:ctor-factory-priv}, but hopefully the additional discussion provided
insight into critical design decisions.}