1
0
Fork 0

interface() no longer returns a usable class

- Number of reasons. First and foremost, it doesn't make sense to return a usable class with no body/definition
- Secondly, to make the following commit(s) possible and consistent
closure/master
Mike Gerwitz 2011-03-05 02:59:21 -05:00
parent ace9f4c1ea
commit 0f9454b79b
2 changed files with 87 additions and 20 deletions

View File

@ -100,12 +100,11 @@ module.exports.extend = function( base )
*/ */
module.exports.implement = function() module.exports.implement = function()
{ {
var args = Array.prototype.slice.call( arguments ); // implement on empty base
return createImplement(
// apply to an empty (new) object module.exports.extend(),
args.unshift( module.exports.extend() ); Array.prototype.slice.call( arguments )
);
return implement.apply( this, args );
}; };
@ -313,6 +312,37 @@ function createStaging( cname )
} }
/**
* Creates an intermediate object to permit implementing interfaces
*
* This object defers processing until extend() is called. This intermediate
* object ensures that a usable class is not generated until after extend() is
* called, as it does not make sense to create a class without any
* body/definition.
*
* @param {Object} base base class to implement atop of
* @param {Array} ifaces interfaces to implement
*
* @return {Object} intermediate implementation object
*/
function createImplement( base, ifaces )
{
ifaces.unshift( base );
// Defer processing until after extend(). This also ensures that implement()
// returns nothing usable.
return {
extend: function( def )
{
return extend.apply( null, [
implement.apply( this, ifaces ),
def
] );
},
};
}
/** /**
* Creates extend function * Creates extend function
* *
@ -714,6 +744,8 @@ function attachExtend( func )
/** /**
* Attaches implement method to the given function (class) * Attaches implement method to the given function (class)
* *
* Please see the implement() export of this module for more information.
*
* @param {function()} func function (class) to attach method to * @param {function()} func function (class) to attach method to
* *
* @return {undefined} * @return {undefined}
@ -722,10 +754,10 @@ function attachImplement( func )
{ {
util.defineSecureProp( func, 'implement', function() util.defineSecureProp( func, 'implement', function()
{ {
var args = Array.prototype.slice.call( arguments ); return createImplement(
args.unshift( func ); func,
Array.prototype.slice.call( arguments )
return implement.apply( this, args ); );
}); });
} }

View File

@ -64,19 +64,41 @@ var Type = Interface.extend( {
{ {
assert.doesNotThrow( function() assert.doesNotThrow( function()
{ {
Foo = Class.implement( Type, Type2 ); Class.implement( Type, Type2 );
}, Error, "Class can implement interfaces" ); }, Error, "Class can implement interfaces" );
} )();
assert.ok(
( Class.isClass( Foo ) ), /**
"Class returned from implementing interfaces on an empty base is a " + * Initially, the implement() method returned an abstract class. However, it
"valid class" * doesn't make sense to create a class without any actual definition (and
* there's other implementation considerations that caused this route to be
* taken). One wouldn't do "class Foo implements Type", and not provide any
* body.
*
* Therefore, implement() should return nothing useful until extend() is called
* on it.
*/
( function testResultOfImplementIsNotUsableAsAClass()
{
var result = Class.implement( Type );
assert.equal(
( Class.isClass( result ) ),
false,
"Result of implement operation on class is not usable as a Class"
); );
} )(); } )();
/**
* As a consequence of the above, we must extend with an empty definition
* (base) in order to get our abstract class.
*/
( function testAbstractMethodsCopiedIntoNewClassUsingEmptyBase() ( function testAbstractMethodsCopiedIntoNewClassUsingEmptyBase()
{ {
Foo = Class.implement( Type, Type2 ).extend( {} );
assert.ok( assert.ok(
( ( Foo.prototype.foo instanceof Function ) ( ( Foo.prototype.foo instanceof Function )
&& ( Foo.prototype.foo2 instanceof Function ) && ( Foo.prototype.foo2 instanceof Function )
@ -90,19 +112,32 @@ var Type = Interface.extend( {
{ {
assert.doesNotThrow( function() assert.doesNotThrow( function()
{ {
PlainFoo2 = PlainFoo.implement( Type, Type2 ); PlainFoo.implement( Type, Type2 );
}, Error, "Classes can implement interfaces" ); }, Error, "Classes can implement interfaces" );
} )();
assert.ok(
( Class.isClass( PlainFoo2 ) ), /**
"Class returned from implementing interfaces on an existing base is a " + * Ensure the same system mentioned above also applies to the extend() method on
"valid class" * existing classes
*/
( function testImplementingInterfaceAtopExistingClassIsNotUsableByDefault()
{
var result = PlainFoo.implement( Type );
assert.equal(
( Class.isClass( result ) ),
false,
"Result of implementing interfaces on an existing base is not " +
"usable as a Class"
); );
} )(); } )();
( function testAbstractMethodsCopiedIntoNewClassUsingExistingBase() ( function testAbstractMethodsCopiedIntoNewClassUsingExistingBase()
{ {
PlainFoo2 = PlainFoo.implement( Type, Type2 ).extend( {} );
assert.ok( assert.ok(
( ( PlainFoo2.prototype.foo instanceof Function ) ( ( PlainFoo2.prototype.foo instanceof Function )
&& ( PlainFoo2.prototype.foo2 instanceof Function ) && ( PlainFoo2.prototype.foo2 instanceof Function )