1
0
Fork 0

Encapsulating implemented list (can use isInstanceOf() or a future reflection API)

closure/master
Mike Gerwitz 2011-01-03 23:41:45 -05:00
parent 0b112dac51
commit c3b00ab9b7
2 changed files with 61 additions and 42 deletions

View File

@ -24,6 +24,16 @@
var util = require( './util' ); var util = require( './util' );
/**
* Stores class metadata internally (ensures data is encapsulated)
*
* The data in this object is hashed on the class object itself. Therefore, to
* look up an item, you would provide the class itself as the property.
*
* @type {Object.<Object, { implemented: Array.<string> }>}
*/
var class_meta = {};
/** /**
* Creates a class, inheriting either from the provided base class or the * Creates a class, inheriting either from the provided base class or the
@ -66,13 +76,7 @@ exports.implement = function()
} }
var class_new = exports.extend( dest ); var class_new = exports.extend( dest );
getMeta( class_new ).implemented = implemented;
// we cannot reassign the value since it is frozen, so copy the values into
// the array
while ( implemented[ 0 ] )
{
class_new.implemented.push( implemented.shift() );
}
return class_new; return class_new;
}; };
@ -130,6 +134,8 @@ exports.isClassInstance = function( obj )
*/ */
exports.isInstanceOf = function( type, instance ) exports.isInstanceOf = function( type, instance )
{ {
var meta, implemented, i;
try try
{ {
// check prototype chain (with throw an error if type is not a // check prototype chain (with throw an error if type is not a
@ -141,16 +147,22 @@ exports.isInstanceOf = function( type, instance )
} }
catch ( e ) {} catch ( e ) {}
var implemented = type.implemented; // if no metadata is available, then our remaining checks cannot be
if ( implemented instanceof Array ) // performed
if ( !( meta = getMeta( type ) ) )
{ {
var i = implemented.length; return false;
while ( i-- ) }
implemented = meta.implemented;
i = implemented.length;
// check implemented interfaces
while ( i-- )
{
if ( implemented[ i ] === type )
{ {
if ( implemented[ i ] === type ) return true;
{
return true;
}
} }
} }
@ -272,6 +284,9 @@ var extend = ( function( extending )
// members at runtime // members at runtime
util.freeze( new_class ); util.freeze( new_class );
// create internal metadata for the new class
createMeta( new_class );
// we're done with the extension process // we're done with the extension process
extending = false; extending = false;
@ -352,7 +367,6 @@ function setupProps( func, abstract_methods )
{ {
attachAbstract( func, abstract_methods ); attachAbstract( func, abstract_methods );
attachExtend( func ); attachExtend( func );
attachImplemented( func );
} }
@ -452,19 +466,6 @@ function attachExtend( func )
} }
/**
* Attaches empty list that may be populated with the implemented interfaces
*
* @param {Function} func function (class) to attach method to
*
* @return {undefined}
*/
function attachImplemented( func )
{
util.defineSecureProp( func, 'implemented', [] );
}
/** /**
* Attaches partially applied isInstanceOf() method to class instance * Attaches partially applied isInstanceOf() method to class instance
* *
@ -483,3 +484,34 @@ function attachInstanceOf( instance )
util.defineSecureProp( instance, 'isA', method ); util.defineSecureProp( instance, 'isA', method );
} }
/**
* Initializes class metadata for the given class
*
* @param {Class} obj class to initialize metadata for
*
* @return {undefined}
*/
function createMeta( obj )
{
class_meta[ obj ] = {
implemented: [],
};
}
/**
* Returns reference to metadata for the requested class
*
* Since a reference is returned (rather than a copy), the returned object can
* be modified to alter the metadata.
*
* @param {Class} obj class to retrieve metadata for
*
* @return {Object}
*/
function getMeta( obj )
{
return class_meta[ obj ];
}

View File

@ -45,24 +45,11 @@ assert.ok(
var Foo = {}, var Foo = {},
PlainFoo = Class.extend(); PlainFoo = Class.extend();
assert.ok(
( PlainFoo.implemented instanceof Array ),
"Class contains empty list of implemented interfaces if " +
"none are implemented"
);
assert.doesNotThrow( function() assert.doesNotThrow( function()
{ {
Foo = Class.implement( Type, Type2 ); Foo = Class.implement( Type, Type2 );
}, Error, "Class can implement interfaces" ); }, Error, "Class can implement interfaces" );
assert.ok(
( ( Foo.implemented[ 0 ] === Type )
&& ( Foo.implemented[ 1 ] === Type2 )
),
"Class contains list of implemented interfaces"
);
assert.ok( assert.ok(
( ( Foo.prototype.foo instanceof Function ) ( ( Foo.prototype.foo instanceof Function )
&& ( Foo.prototype.foo2 instanceof Function ) && ( Foo.prototype.foo2 instanceof Function )