1
0
Fork 0

[#19] Removed 'final' keyword and all associated logic

- Kept FinalClass'
closure/master
Mike Gerwitz 2011-06-08 01:21:07 -04:00
parent 8b83e85c43
commit 4fea62a8ed
6 changed files with 5 additions and 303 deletions

View File

@ -74,8 +74,6 @@ ease.js, until such a point where prototypes are no longer adequate.
* Member Visibility:: Encapsulation is a core concept of Object-Oriented * Member Visibility:: Encapsulation is a core concept of Object-Oriented
programming programming
* Static Members:: Members whose use do not require instantiation * Static Members:: Members whose use do not require instantiation
* Final Classes and Methods:: Declaring classes and methods that cannot be
overridden
@end menu @end menu
@ -1288,97 +1286,3 @@ it is perfectly legal to alter the object:
MyClass.$('foo').a = 'c'; MyClass.$('foo').a = 'c';
@end verbatim @end verbatim
@node Final Classes and Methods
@section Final Classes and Methods
The @dfn{final} keyword is used to denote a class or method that cannot be
overridden by subtypes. This keyword can be used to ensure that the
implementation cannot be altered and is guaranteed to be in a consistent state.
If the final keyword is @emph{not} used, then the developer should be mindful of
how subtypes may alter the logic by overriding members.
Unlike Java, the final keyword cannot be used on properties. Consider using the
@code{@ref{Constants, const}} keyword instead. Much @emph{like} Java, classes
and methods are @emph{not} final by default. This is in contrast to C++, where
members must be explicitly declared as ``virtual'' in order to be overridden.
Members may be declared as final by simply prefixing the definition with the
@code{final} keyword. Consider the following example in which we define class
@var{ChocolateFactory}. The factory is able to accommodate different types of
chocolate by allowing the process at each station to be altered, but the order
in which the chocolate reaches these stations @emph{cannot} be altered. In order
to ensure this order is preserved, we will declare our template method,
@code{create()}, as final. We will then permit subtypes to override the methods
that represent each of the stations, permitting different types of chocolate to
be produced.
@float Figure, f:final-methods
@verbatim
var ChocolateFactory = Class( 'ChocolateFactory',
'final public create': function()
{
this.initFactory();
this.combineIngredients();
return this.shapePieces();
},
'protected initFactory': function()
{
},
'protected combineIngredients': function()
{
},
'protected shapePieces': function()
{
},
);
@end verbatim
@caption{Using final methods to prevent subtypes from altering an
implementation}
@end float
In the above example, subtypes would receive an error if they attempted to
override the @code{create()} method. They are, however, permitted to override
the other methods.
Now let us consider an alternative implementation. Rather than permitting
subtypes to override functionality, we may decide to use various chocolate
strategies (Strategy pattern, GoF). In this case, we wish to declare the class
as final, preventing it from being overridden. Instead, we will accept a
strategy representing the type of chocolate to be created.
@float Figure, f:final-class
@verbatim
// declare the class as final
var ChocolateFactory = FinalClass( 'ChocolateFactory',
'private _strategy': null,
'public __construct': function( strategy )
{
this._strategy = strategy;
},
'public create': function()
{
this._strategy.initFactory();
this._strategy.combineIngredients();
return this._strategy.shapePieces();
},
);
// assuming MilkChocolate, DarkChocolate and WhiteChocolate are all
// strategies
ChocolateFactory( MilkChocolate() ).create();
ChocolateFactory( DarkChocolate() ).create();
ChocolateFactory( WhiteChocolate() ).create();
@end verbatim
@caption{Declaring final classes}
@end float
Any attempt to override @var{ChocolateFactory} would produce an error. Note
that, because the class is declared as final, it is unnecessary (and redundant,
though not disallowed) to declare its methods as final.

View File

@ -134,14 +134,6 @@ function validateMethod( keywords, prev_data, value, name )
"Method '" + name + "' cannot be both private and abstract" "Method '" + name + "' cannot be both private and abstract"
); );
} }
// abstract final also does not make sense
if ( keywords[ 'final' ] )
{
throw TypeError(
"Method '" + name + "' cannot be both abstract and final"
);
}
} }
// const doesn't make sense for methods; they're always immutable // const doesn't make sense for methods; they're always immutable
@ -251,16 +243,6 @@ exports.buildProp = function( members, meta, name, value, keywords, base )
); );
} }
// nor do final properties, because of late static binding (const should be
// used instead; read up on the rationale in the documentation)
if ( keywords[ 'final' ] )
{
throw TypeError(
"Property '" + name + "' cannot be declared as final; consider " +
"const"
);
}
if ( keywords[ 'static' ] && keywords[ 'const' ] ) if ( keywords[ 'static' ] && keywords[ 'const' ] )
{ {
throw TypeError( throw TypeError(

View File

@ -31,7 +31,6 @@ var _keywords = {
'protected': true, 'protected': true,
'private': true, 'private': true,
'static': true, 'static': true,
'final': true,
'abstract': true, 'abstract': true,
'const': true, 'const': true,
'virtual': true, 'virtual': true,

View File

@ -1,183 +0,0 @@
/**
* Tests final members
*
* Copyright (C) 2010 Mike Gerwitz
*
* This file is part of ease.js.
*
* ease.js is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Mike Gerwitz
* @package test
*/
var common = require( './common' ),
assert = require( 'assert' ),
builder = common.require( 'class_builder' ),
Class = common.require( 'class' )
FinalClass = common.require( 'class_final' )
;
/**
* Methods declared as final should not be able to be overridden by subtypes.
* Simple as that.
*/
( function testFinalMethodsCannotBeOverridenBySubtypes()
{
var Foo = builder.build(
{
'final public foo': function() {},
} );
try
{
// attempt to override (should fail)
builder.build( Foo,
{
'public foo': function() {},
} );
}
catch ( e )
{
assert.ok(
e.message.search( 'foo' ) !== -1,
"Final error message contains name of method"
);
return;
}
assert.fail( 'Should not be able to override final methods' );
} )();
/**
* Due to our static implementation (late static binding), the use of 'final'
* with properties doesn't make much sense. We have to deal with different
* problems than languages like Java that truly bind members statically. Consult
* the documentation for rationale.
*
* See also const keyword.
*/
( function testFinalKeywordCannotBeUsedWithProperties()
{
try
{
// attempt to define a 'final' property (should fail)
builder.build(
{
'final public foo': 'bar',
} );
}
catch ( e )
{
assert.ok(
e.message.search( 'foo' ) !== -1,
"Final property error message contains name of property"
);
return;
}
assert.fail( "Should not be able to use final keyword with properties" );
} )();
/**
* The 'abstract' keyword's very point is to state that no definition is
* provided and that a subtype must provide one. Therefore, declaring something
* 'abstract final' is rather contradictory and should not be permitted.
*/
( function testFinalyKeywordCannotBeUsedWithAbstract()
{
try
{
// should fail
builder.build( { 'abstract final foo': [] } );
}
catch ( e )
{
assert.ok(
e.message.search( 'foo' ) !== -1,
"Abstract final error message contains name of method"
);
return;
}
assert.fail( "Should not be able to use final keyword with abstract" );
} )();
/**
* Ensure that FinalClass properly forwards data to create a new Class.
*/
( function testFinalClassesAreValidClasses()
{
assert.ok( Class.isClass( FinalClass( {} ) ),
"Final classes should generate valid classes"
);
} )();
/**
* When a class is declared as final, it should prevent it from ever being
* extended. Ever.
*/
( function testFinalClassesCannotBeExtended()
{
try
{
// this should fail
FinalClass( 'Foo', {} ).extend( {} );
}
catch ( e )
{
assert.ok(
e.message.search( 'Foo' ) !== -1,
"Final class error message should contain name of class"
);
return;
}
assert.fail( "Should not be able to extend final classes" );
} )();
/**
* Ensure we're able to create final classes by extending existing classes.
*/
( function testCanCreateFinalSubtypes()
{
var Foo = builder.build( {} ),
FinalNamed = FinalClass( 'FinalNamed' ).extend( Foo, {} ),
FinalAnon = FinalClass.extend( Foo, {} )
;
// named
assert.throws( function()
{
FinalNamed.extend( {} );
}, Error, "Cannot extend final named subtype" );
// anonymous
assert.throws( function()
{
FinalAnon.extend( {} );
}, Error, "Cannot extend final anonymous subtype" );
} )();

View File

@ -26,7 +26,7 @@ var common = require( './common' ),
assert = require( 'assert' ), assert = require( 'assert' ),
parse = common.require( 'prop_parser' ).parseKeywords, parse = common.require( 'prop_parser' ).parseKeywords,
data = parse( 'final static abstract foo' ), data = parse( 'virtual static abstract foo' ),
keywords = data.keywords keywords = data.keywords
; ;
@ -44,7 +44,7 @@ var common = require( './common' ),
( function testProperlyRetrievesAllKeywords() ( function testProperlyRetrievesAllKeywords()
{ {
assert.ok( assert.ok(
( ( keywords['final'] === true ) ( ( keywords['virtual'] === true )
&& ( keywords['static'] === true ) && ( keywords['static'] === true )
&& ( keywords['abstract'] === true ) && ( keywords['abstract'] === true )
), ),
@ -63,7 +63,7 @@ var common = require( './common' ),
{ {
// Odd seeing these all together, isn't it? Note that this is not at all // Odd seeing these all together, isn't it? Note that this is not at all
// valid, but the prop parser doesn't care what appears together. // valid, but the prop parser doesn't care what appears together.
parse( 'public protected private static final abstract const var' ); parse( 'public protected private static virtual abstract const var' );
}, Error, "Known keywords are permitted by the parser" ); }, Error, "Known keywords are permitted by the parser" );
var oddword = 'foobunny', var oddword = 'foobunny',

View File

@ -131,7 +131,7 @@ var common = require( './common' ),
'const foo2': '', 'const foo2': '',
'public private const foo3': '', 'public private const foo3': '',
'public static final method': function() {}, 'public static virtual method': function() {},
// tricky tricky (lots of spaces) // tricky tricky (lots of spaces)
'public const spaces': function() {}, 'public const spaces': function() {},
@ -144,7 +144,7 @@ var common = require( './common' ),
foo2: { 'const': true }, foo2: { 'const': true },
foo3: { 'public': true, 'private': true, 'const': true }, foo3: { 'public': true, 'private': true, 'const': true },
method: { 'public': true, 'static': true, 'final': true }, method: { 'public': true, 'static': true, 'virtual': true },
spaces: { 'public': true, 'const': true }, spaces: { 'public': true, 'const': true },
} }