Beginning of Trait and Class.use
This is a rough concept showing how traits will be used at definition time by classes (note that this does not yet address how they will be ``mixed in'' at the time of instantiation).perfodd
parent
44a45d1f44
commit
62035a0b4c
|
@ -0,0 +1,76 @@
|
||||||
|
/**
|
||||||
|
* Provides system for code reuse via traits
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Mike Gerwitz
|
||||||
|
*
|
||||||
|
* This file is part of GNU ease.js.
|
||||||
|
*
|
||||||
|
* ease.js is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function Trait()
|
||||||
|
{
|
||||||
|
switch ( arguments.length )
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return Trait.extend.apply( this, arguments );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Trait.extend = function( dfn )
|
||||||
|
{
|
||||||
|
function TraitType()
|
||||||
|
{
|
||||||
|
throw Error( "Cannot instantiate trait" );
|
||||||
|
};
|
||||||
|
|
||||||
|
TraitType.__trait = true;
|
||||||
|
TraitType.__dfn = dfn;
|
||||||
|
|
||||||
|
return TraitType;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Trait.isTrait = function( trait )
|
||||||
|
{
|
||||||
|
return !!( trait || {} ).__trait;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mix trait into the given definition
|
||||||
|
*
|
||||||
|
* The original object DFN is modified; it is not cloned.
|
||||||
|
*
|
||||||
|
* @param {Trait} trait trait to mix in
|
||||||
|
* @param {Object} dfn definition object to merge into
|
||||||
|
*
|
||||||
|
* @return {Object} dfn
|
||||||
|
*/
|
||||||
|
Trait.mixin = function( trait, dfn )
|
||||||
|
{
|
||||||
|
var tdfn = trait.__dfn || {};
|
||||||
|
for ( var f in tdfn )
|
||||||
|
{
|
||||||
|
dfn[ f ] = tdfn[ f ];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dfn;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = Trait;
|
33
lib/class.js
33
lib/class.js
|
@ -28,6 +28,8 @@ var util = require( __dirname + '/util' ),
|
||||||
MethodWrapperFactory = require( __dirname + '/MethodWrapperFactory' ),
|
MethodWrapperFactory = require( __dirname + '/MethodWrapperFactory' ),
|
||||||
wrappers = require( __dirname + '/MethodWrappers' ).standard,
|
wrappers = require( __dirname + '/MethodWrappers' ).standard,
|
||||||
|
|
||||||
|
Trait = require( __dirname + '/Trait' ),
|
||||||
|
|
||||||
class_builder = ClassBuilder(
|
class_builder = ClassBuilder(
|
||||||
require( __dirname + '/MemberBuilder' )(
|
require( __dirname + '/MemberBuilder' )(
|
||||||
MethodWrapperFactory( wrappers.wrapNew ),
|
MethodWrapperFactory( wrappers.wrapNew ),
|
||||||
|
@ -120,6 +122,16 @@ module.exports.implement = function( interfaces )
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports.use = function( traits )
|
||||||
|
{
|
||||||
|
// consume traits onto an empty base
|
||||||
|
return createUse(
|
||||||
|
null,
|
||||||
|
Array.prototype.slice.call( arguments )
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the provided object is a class created through ease.js
|
* Determines whether the provided object is a class created through ease.js
|
||||||
*
|
*
|
||||||
|
@ -359,6 +371,27 @@ function createImplement( base, ifaces, cname )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createUse( base, traits )
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
extend: function()
|
||||||
|
{
|
||||||
|
var args = Array.prototype.slice.call( arguments ),
|
||||||
|
dfn = args.pop(),
|
||||||
|
base = args.pop();
|
||||||
|
|
||||||
|
// "mix" each trait into the provided definition object
|
||||||
|
for ( var i = 0, n = traits.length; i < n; i++ )
|
||||||
|
{
|
||||||
|
Trait.mixin( traits[ i ], dfn );
|
||||||
|
}
|
||||||
|
|
||||||
|
return extend.call( null, base, dfn );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mimics class inheritance
|
* Mimics class inheritance
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
/**
|
||||||
|
* Tests basic trait definition
|
||||||
|
*
|
||||||
|
* Copyright (C) 2014 Mike Gerwitz
|
||||||
|
*
|
||||||
|
* This file is part of GNU ease.js.
|
||||||
|
*
|
||||||
|
* ease.js is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require( 'common' ).testCase(
|
||||||
|
{
|
||||||
|
caseSetUp: function()
|
||||||
|
{
|
||||||
|
this.Sut = this.require( 'Trait' );
|
||||||
|
this.Class = this.require( 'class' );
|
||||||
|
|
||||||
|
// means of creating anonymous traits
|
||||||
|
this.ctor = [
|
||||||
|
this.Sut.extend,
|
||||||
|
this.Sut,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We continue with the same concept used for class
|
||||||
|
* definitions---extending the Trait module itself will create an
|
||||||
|
* anonymous trait.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Can extend Trait to create anonymous trait': function( T )
|
||||||
|
{
|
||||||
|
this.assertOk( this.Sut.isTrait( T( {} ) ) );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A trait can only be used by something else---it does not make sense
|
||||||
|
* to instantiate them directly, since they form an incomplete picture.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Cannot instantiate trait without error': function( T )
|
||||||
|
{
|
||||||
|
this.assertThrows( function()
|
||||||
|
{
|
||||||
|
T( {} )();
|
||||||
|
}, Error );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One way that traits acquire meaning is by their use in creating
|
||||||
|
* classes. This also allows us to observe whether traits are actually
|
||||||
|
* working as intended without testing too closely to their
|
||||||
|
* implementation. This test simply ensures that the Class module will
|
||||||
|
* accept our traits.
|
||||||
|
*
|
||||||
|
* Classes consume traits as part of their definition using the `use'
|
||||||
|
* method. We should be able to then invoke the `extend' method to
|
||||||
|
* provide our own definition, without having to inherit from another
|
||||||
|
* class.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Base class definition is applied when using traits':
|
||||||
|
function( T )
|
||||||
|
{
|
||||||
|
var expected = 'bar';
|
||||||
|
|
||||||
|
var C = this.Class.use( T( {} ) ).extend(
|
||||||
|
{
|
||||||
|
foo: expected,
|
||||||
|
} );
|
||||||
|
|
||||||
|
this.assertOk( this.Class.isClass( C ) );
|
||||||
|
this.assertEqual( C().foo, expected );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traits contribute to the definition of the class that `use's them;
|
||||||
|
* therefore, it would stand to reason that we should still be able to
|
||||||
|
* inherit from a supertype while using traits.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Supertype definition is applied when using traits':
|
||||||
|
function( T )
|
||||||
|
{
|
||||||
|
var expected = 'bar';
|
||||||
|
expected2 = 'baz';
|
||||||
|
Foo = this.Class( { foo: expected } ),
|
||||||
|
SubFoo = this.Class.use( T( {} ) )
|
||||||
|
.extend( Foo, { bar: expected2 } );
|
||||||
|
|
||||||
|
var inst = SubFoo();
|
||||||
|
|
||||||
|
this.assertOk( this.Class.isA( Foo, inst ) );
|
||||||
|
this.assertEqual( inst.foo, expected, "Supertype failure" );
|
||||||
|
this.assertEqual( inst.bar, expected2, "Subtype failure" );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The above tests have ensured that classes are still operable with
|
||||||
|
* traits; we can now test that traits are mixed into the class
|
||||||
|
* definition via `use' by asserting on the trait definitions.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Trait definition is mixed into base class definition':
|
||||||
|
function( T )
|
||||||
|
{
|
||||||
|
var called = false;
|
||||||
|
|
||||||
|
var Trait = T( { foo: function() { called = true; } } ),
|
||||||
|
inst = this.Class.use( Trait ).extend( {} )();
|
||||||
|
|
||||||
|
// if mixin was successful, then we should have the `foo' method.
|
||||||
|
this.assertDoesNotThrow( function()
|
||||||
|
{
|
||||||
|
inst.foo();
|
||||||
|
}, Error, "Should have access to mixed in fields" );
|
||||||
|
|
||||||
|
// if our variable was not set, then it was a bs copy
|
||||||
|
this.assertOk( called, "Mixed in field copy error" );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The above test should apply just the same to subtypes.
|
||||||
|
*/
|
||||||
|
'@each(ctor) Trait definition is mixed into subtype definition':
|
||||||
|
function( T )
|
||||||
|
{
|
||||||
|
var called = false;
|
||||||
|
|
||||||
|
var Trait = T( { foo: function() { called = true; } } ),
|
||||||
|
Foo = this.Class( {} ),
|
||||||
|
inst = this.Class.use( Trait ).extend( Foo, {} )();
|
||||||
|
|
||||||
|
inst.foo();
|
||||||
|
this.assertOk( called );
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// At this point, we assume that each ctor method is working as expected
|
||||||
|
// (that is---the same); we will proceed to test only a single method of
|
||||||
|
// construction under that assumption.
|
||||||
|
//
|
||||||
|
} );
|
Loading…
Reference in New Issue