From 0f9454b79b14e46873758642a3f6fd27e954eada Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Sat, 5 Mar 2011 02:59:21 -0500 Subject: [PATCH] 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 --- lib/class.js | 52 +++++++++++++++++++++++++++------- test/test-class-implement.js | 55 +++++++++++++++++++++++++++++------- 2 files changed, 87 insertions(+), 20 deletions(-) diff --git a/lib/class.js b/lib/class.js index 44c4411..33b8ccf 100644 --- a/lib/class.js +++ b/lib/class.js @@ -100,12 +100,11 @@ module.exports.extend = function( base ) */ module.exports.implement = function() { - var args = Array.prototype.slice.call( arguments ); - - // apply to an empty (new) object - args.unshift( module.exports.extend() ); - - return implement.apply( this, args ); + // implement on empty base + return createImplement( + module.exports.extend(), + Array.prototype.slice.call( arguments ) + ); }; @@ -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 * @@ -714,6 +744,8 @@ function attachExtend( func ) /** * 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 * * @return {undefined} @@ -722,10 +754,10 @@ function attachImplement( func ) { util.defineSecureProp( func, 'implement', function() { - var args = Array.prototype.slice.call( arguments ); - args.unshift( func ); - - return implement.apply( this, args ); + return createImplement( + func, + Array.prototype.slice.call( arguments ) + ); }); } diff --git a/test/test-class-implement.js b/test/test-class-implement.js index cfb973a..ff46d61 100644 --- a/test/test-class-implement.js +++ b/test/test-class-implement.js @@ -64,19 +64,41 @@ var Type = Interface.extend( { { assert.doesNotThrow( function() { - Foo = Class.implement( Type, Type2 ); + Class.implement( Type, Type2 ); }, Error, "Class can implement interfaces" ); +} )(); - assert.ok( - ( Class.isClass( Foo ) ), - "Class returned from implementing interfaces on an empty base is a " + - "valid class" + +/** + * Initially, the implement() method returned an abstract class. However, it + * 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() { + Foo = Class.implement( Type, Type2 ).extend( {} ); + assert.ok( ( ( Foo.prototype.foo instanceof Function ) && ( Foo.prototype.foo2 instanceof Function ) @@ -90,19 +112,32 @@ var Type = Interface.extend( { { assert.doesNotThrow( function() { - PlainFoo2 = PlainFoo.implement( Type, Type2 ); + PlainFoo.implement( Type, Type2 ); }, Error, "Classes can implement interfaces" ); +} )(); - assert.ok( - ( Class.isClass( PlainFoo2 ) ), - "Class returned from implementing interfaces on an existing base is a " + - "valid class" + +/** + * Ensure the same system mentioned above also applies to the extend() method on + * 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() { + PlainFoo2 = PlainFoo.implement( Type, Type2 ).extend( {} ); + assert.ok( ( ( PlainFoo2.prototype.foo instanceof Function ) && ( PlainFoo2.prototype.foo2 instanceof Function )