[#5] Added additional visibility rationale examples
parent
9aaef9736e
commit
4c4c716596
|
@ -510,7 +510,7 @@ inst.push( 'foo' );
|
||||||
console.log( inst._data ); // [ 'foo' ]
|
console.log( inst._data ); // [ 'foo' ]
|
||||||
|
|
||||||
// uh oh.
|
// uh oh.
|
||||||
inst.pop( 'foo' ); // foo
|
inst.pop(); // foo
|
||||||
console.log( inst._data ); // []
|
console.log( inst._data ); // []
|
||||||
@end verbatim
|
@end verbatim
|
||||||
@caption{Working easily with instance members in JavaScript breaks
|
@caption{Working easily with instance members in JavaScript breaks
|
||||||
|
@ -704,6 +704,196 @@ This method of storing instance data was ease.js's initial ``proof-of-concept''
|
||||||
implementation (@pxref{Class Storage}). Clearly, this was not going to work;
|
implementation (@pxref{Class Storage}). Clearly, this was not going to work;
|
||||||
some changes to this implementation were needed.
|
some changes to this implementation were needed.
|
||||||
|
|
||||||
|
@subsubsection Instance Memory Considerations
|
||||||
|
JavaScript does not provide destructors to let us know when an instance is about
|
||||||
|
to be GC'd, so we unfortunately cannot know when to free instance data from
|
||||||
|
memory in @ref{f:js-encapsulate-instance}. We are also not provided with an API
|
||||||
|
that can return the reference count for a given object. We could provide a
|
||||||
|
method that the user could call when they were done with the object, but that is
|
||||||
|
not natural to a JavaScript developer and they could easily forget to call the
|
||||||
|
method.
|
||||||
|
|
||||||
|
As such, it seems that the only solution for this rather large issue is to store
|
||||||
|
instance data on the instance itself so that it will be freed with the instance
|
||||||
|
when it is garbage collected (remember, we decided that privileged members were
|
||||||
|
not an option in the discussion of @ref{f:js-privileged-members}). Hold on - we
|
||||||
|
already did that in @ref{f:js-proto-inst-noencapsulate}; that caused our data to
|
||||||
|
be available publicly. How do we approach this situation?
|
||||||
|
|
||||||
|
If we are adding data to an instance itself, there is no way to prevent it from
|
||||||
|
being accessed in some manner, making true encapsulation impossible. The only
|
||||||
|
options are to obscure it as best as possible, to make it too difficult to
|
||||||
|
access in any sane implementation. For example:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
The property storing the private data could be made non-enumerable, requiring
|
||||||
|
the use of a debugger or looking at the source code to determine the object
|
||||||
|
name.
|
||||||
|
@itemize
|
||||||
|
@item This would work only with ECMAScript 5 and later environments.
|
||||||
|
@end itemize
|
||||||
|
@item
|
||||||
|
We could store all private data in an obscure property name, such as
|
||||||
|
@var{___$$priv$$___}, which would make it clear that it should not be accessed.
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
We could take that a step further and randomize the name, making it very
|
||||||
|
difficult to discover at runtime, especially if it were
|
||||||
|
non-enumerable@footnote{Note that ease.js does not currently randomize its
|
||||||
|
visibility object name.}.
|
||||||
|
@end itemize
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Regardless, it is clear that our data will only be ``encapsulated'' in the sense
|
||||||
|
that it will not be available conveniently via a public API. Let's take a look
|
||||||
|
at how something like that may work:
|
||||||
|
|
||||||
|
@float Figure, f:js-obscure-private
|
||||||
|
@verbatim
|
||||||
|
var Stack = ( function()
|
||||||
|
{
|
||||||
|
// implementation of getSomeRandomName() is left up to the reader
|
||||||
|
var _privname = getSomeRandomName();
|
||||||
|
|
||||||
|
var S = function()
|
||||||
|
{
|
||||||
|
// define a non-enumerable property to store our private data (will only
|
||||||
|
// work in ES5+ environments)
|
||||||
|
Object.defineProperty( this, _privname, {
|
||||||
|
enumerable: false,
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
|
||||||
|
value: {
|
||||||
|
stack: []
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
S.prototype = {
|
||||||
|
push: function( val )
|
||||||
|
{
|
||||||
|
this[ _privname ].stack.push( val );
|
||||||
|
},
|
||||||
|
|
||||||
|
pop: function()
|
||||||
|
{
|
||||||
|
return this[ _privname ].stack.pop();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return S;
|
||||||
|
} );
|
||||||
|
|
||||||
|
var inst = new Stack();
|
||||||
|
inst.push( 'foo' );
|
||||||
|
inst.pop(); // foo
|
||||||
|
@end verbatim
|
||||||
|
@caption{Using a random, non-enumerable property name to store private members}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
Now we are really starting to hack around what JavaScript provides for us. We
|
||||||
|
seem to be combining the encapsulation issues presented in
|
||||||
|
@ref{f:js-proto-inst-noencapsulate} and the obscurity demonstrated in
|
||||||
|
@ref{f:js-encapsulate-instance}. In addition, we our implementation depends on
|
||||||
|
ECMAScript 5 (ideally, we would detect that and fall back to normal, enumerable
|
||||||
|
properties in pre-ES5 environments, which ease.js does indeed do). This seems to
|
||||||
|
be a case of encapsulation through obscurity@footnote{A play on ``security
|
||||||
|
through obscurity''.}. While our implementation certainly makes it difficult to
|
||||||
|
get at the private member data, it is also very obscure and inconvenient to work
|
||||||
|
with. Who wants to write Object-Oriented code like that?
|
||||||
|
|
||||||
|
@subsubsection Other Considerations
|
||||||
|
We have conveniently omitted a number of other important factors in our
|
||||||
|
discussion thus far. Before continuing, they deserve some mention and careful
|
||||||
|
consideration.
|
||||||
|
|
||||||
|
How would we implement private methods? We could add them to our private member
|
||||||
|
object, just as we defined @var{stack} in @ref{f:js-obscure-private}, but that
|
||||||
|
would cause it to be redefined with each instance, raising the same issues that
|
||||||
|
were discussed with @ref{f:js-privileged-members}. Therefore, we would have to
|
||||||
|
define them in a separate ``prototype'', if you will, that only we have access
|
||||||
|
to:
|
||||||
|
|
||||||
|
@float Figure, f:js-obscure-private-methods
|
||||||
|
@verbatim
|
||||||
|
var Stack = ( function()
|
||||||
|
{
|
||||||
|
// implementation of getSomeRandomName() is left up to the reader
|
||||||
|
var _privname = getSomeRandomName();
|
||||||
|
|
||||||
|
var S = function()
|
||||||
|
{
|
||||||
|
// define a non-enumerable property to store our private data (will only
|
||||||
|
// work in ES5+ environments)
|
||||||
|
Object.defineProperty( this, _privname, {
|
||||||
|
// ... (see previous example)
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
// private methods that only we will have access to
|
||||||
|
var priv_methods = {
|
||||||
|
getStack: function()
|
||||||
|
{
|
||||||
|
// note that, in order for 'this' to be bound to our instance, it
|
||||||
|
// must be passed as first argument to call() or apply()
|
||||||
|
return this[ _privname ].stack;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// public methods
|
||||||
|
S.prototype = {
|
||||||
|
push: function( val )
|
||||||
|
{
|
||||||
|
var stack = priv_methods.getStack.call( this );
|
||||||
|
stack.push( val );
|
||||||
|
},
|
||||||
|
|
||||||
|
pop: function()
|
||||||
|
{
|
||||||
|
var stack = priv_methods.getStack.call( this );
|
||||||
|
return stack.pop();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return S;
|
||||||
|
} );
|
||||||
|
|
||||||
|
var inst = new Stack();
|
||||||
|
inst.push( 'foo' );
|
||||||
|
inst.pop(); // foo
|
||||||
|
@end verbatim
|
||||||
|
@caption{A possible private method implementation}
|
||||||
|
@end float
|
||||||
|
|
||||||
|
While this does solve our problem, it further reduces code clarity. The
|
||||||
|
implementation in @ref{f:js-obscure-private-methods} is certainly a far cry from
|
||||||
|
something like @samp{this._getStack()}, which is all you would need to do in
|
||||||
|
ease.js.
|
||||||
|
|
||||||
|
Another consideration is a protected (@pxref{Access Modifiers}) member
|
||||||
|
implementation, the idea being that subtypes should inherit both public and
|
||||||
|
protected members. Inheritance is not something that we had to worry about with
|
||||||
|
private members, so this adds an entirely new layer of complexity to the
|
||||||
|
implementation. This would mean somehow making a protected prototype available
|
||||||
|
to subtypes through the public prototype. Given our implementation in the
|
||||||
|
previous figures, this would likely mean an awkward call that somewhat
|
||||||
|
resembles: @samp{this[ _protname ].name}.
|
||||||
|
|
||||||
|
Although the implementations show in @ref{f:js-obscure-private} and
|
||||||
|
@ref{f:js-obscure-private-methods} represent a creative hack, this is precisely
|
||||||
|
one of the reasons ease.js was created - to encapsulate such atrocities that
|
||||||
|
would make code that is difficult to use, hard to maintain and easy to introduce
|
||||||
|
bugs. One shouldn't have to have a deep understanding of JavaScript's prototype
|
||||||
|
model in order to write the most elementary of Classical Object-Oriented code.
|
||||||
|
For example, the constructors in the aforementioned figures directly set up an
|
||||||
|
object in which to store private members. ease.js will do this for you before
|
||||||
|
calling the @code{__construct()} method. Furthermore, ease.js does not require
|
||||||
|
referencing that object directly, like we must do in our methods in
|
||||||
|
@ref{f:js-obscure-private}. Nor does ease.js have an awkward syntax for invoking
|
||||||
|
private methods. We will explore how this is handled in the following section.
|
||||||
|
|
||||||
|
|
||||||
@node Internal Methods/Objects
|
@node Internal Methods/Objects
|
||||||
@section Internal Methods/Objects
|
@section Internal Methods/Objects
|
||||||
|
|
Loading…
Reference in New Issue