diff --git a/TODO b/TODO index deafa05..c6296e9 100644 --- a/TODO +++ b/TODO @@ -18,7 +18,6 @@ visibility, than the abstract implementation defined - Abstract keyword cannot be used with private members - toString() must be public - - Should not be able to instantiate object with non-public constructor (singleton) Documentation - Ensure all docblocks contain only valid JSDoc tags diff --git a/doc/classes.texi b/doc/classes.texi index bc7c202..7ca21ea 100644 --- a/doc/classes.texi +++ b/doc/classes.texi @@ -243,6 +243,24 @@ major difference. Returning an object in the constructor does @emph{not} return that object instead of the new class instance, since this does not make sense in a Class-based model. +If you wish to prevent a class from being instantiated, simply throw an +exception within the constructor. This is useful if the class is intended to +provide only static methods, or if you wish to enforce a single instance +(one means of achieving a Singleton). + +@float Figure, f:constructor-prevent +@verbatim + var Foo = Class( 'Foo', + { + 'public __construct': function( name ) + { + throw Error( "Cannot instantiate class Foo" ); + } + } ); +@end verbatim +@caption{Prevent class from being instantiated} +@end float + Constructors are optional. By default, nothing is done after the class is instantiated. diff --git a/doc/impl-details.texi b/doc/impl-details.texi index c6992be..ee69a01 100644 --- a/doc/impl-details.texi +++ b/doc/impl-details.texi @@ -161,6 +161,13 @@ To look up a constructor, one need only search for ``__construct'', rather than the class name. This makes certain operations, such as global searching (using @command{grep} or any other utility), much simpler. +One difference from PHP is the means of preventing instantiation. In PHP, if the +constructor is declared as non-public, then an error will be raised when the +developer attempts to instantiate the class. ease.js did not go this route, as +the method seems cryptic. Instead, an exception should be thrown in the +constructor if the developer doesn't wish the class to be instantiated. In the +future, a common method may be added for consistency/convenience. + The constructor is optional. If one is not provided, nothing is done after the class is instantiated (aside from the internal ease.js initialization tasks). diff --git a/lib/class.js b/lib/class.js index a8a1e08..8a1ecc6 100644 --- a/lib/class.js +++ b/lib/class.js @@ -523,6 +523,15 @@ var extend = ( function( extending ) method: function( name, func, is_abstract, keywords ) { + // constructor check + if ( name === '__construct' ) + { + if ( keywords[ 'protected' ] || keywords[ 'private' ] ) + { + throw TypeError( "Constructor must be public" ); + } + } + member_builder.buildMethod( members, null, name, func, keywords, getMethodInstance, class_id, base diff --git a/test/test-class-constructor.js b/test/test-class-constructor.js index 2262570..6d0fa73 100644 --- a/test/test-class-constructor.js +++ b/test/test-class-constructor.js @@ -146,3 +146,25 @@ assert.ok( "Self-invoking constructor receives arguments" ); + +/** + * In PHP, one would prevent a class from being instantiated by declaring the + * constructor as protected or private. To me, this is cryptic. A better method + * would simply be to throw an exception. Perhaps, in the future, an alternative + * will be provided for consistency. + * + * The constructor must be public. + */ +( function testConstructorCannotBeDeclaredAsProtectedOrPrivate() +{ + assert.throws( function() + { + Class( { 'protected __construct': function() {} } ); + }, TypeError, "Constructor cannot be protected" ); + + assert.throws( function() + { + Class( { 'private __construct': function() {} } ); + }, TypeError, "Constructor cannot be private" ); +} )(); +