From 4fea62a8ed090ef836a3ee8d12bf0a68a05dfc62 Mon Sep 17 00:00:00 2001 From: Mike Gerwitz Date: Wed, 8 Jun 2011 01:21:07 -0400 Subject: [PATCH] [#19] Removed 'final' keyword and all associated logic - Kept FinalClass' --- doc/classes.texi | 96 ------------- lib/member_builder.js | 18 --- lib/prop_parser.js | 1 - test/test-class_builder-final.js | 183 ------------------------ test/test-prop_parser-parse-keywords.js | 6 +- test/test-util-prop-parse-keywords.js | 4 +- 6 files changed, 5 insertions(+), 303 deletions(-) delete mode 100644 test/test-class_builder-final.js diff --git a/doc/classes.texi b/doc/classes.texi index 1387112..e219834 100644 --- a/doc/classes.texi +++ b/doc/classes.texi @@ -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 programming * Static Members:: Members whose use do not require instantiation -* Final Classes and Methods:: Declaring classes and methods that cannot be - overridden @end menu @@ -1288,97 +1286,3 @@ it is perfectly legal to alter the object: MyClass.$('foo').a = 'c'; @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. - diff --git a/lib/member_builder.js b/lib/member_builder.js index 8f53e27..ad4e844 100644 --- a/lib/member_builder.js +++ b/lib/member_builder.js @@ -134,14 +134,6 @@ function validateMethod( keywords, prev_data, value, name ) "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 @@ -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' ] ) { throw TypeError( diff --git a/lib/prop_parser.js b/lib/prop_parser.js index ae49920..929739d 100644 --- a/lib/prop_parser.js +++ b/lib/prop_parser.js @@ -31,7 +31,6 @@ var _keywords = { 'protected': true, 'private': true, 'static': true, - 'final': true, 'abstract': true, 'const': true, 'virtual': true, diff --git a/test/test-class_builder-final.js b/test/test-class_builder-final.js deleted file mode 100644 index 627e37e..0000000 --- a/test/test-class_builder-final.js +++ /dev/null @@ -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 . - * - * @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" ); -} )(); - diff --git a/test/test-prop_parser-parse-keywords.js b/test/test-prop_parser-parse-keywords.js index 0c49fd8..f4ada64 100644 --- a/test/test-prop_parser-parse-keywords.js +++ b/test/test-prop_parser-parse-keywords.js @@ -26,7 +26,7 @@ var common = require( './common' ), assert = require( 'assert' ), parse = common.require( 'prop_parser' ).parseKeywords, - data = parse( 'final static abstract foo' ), + data = parse( 'virtual static abstract foo' ), keywords = data.keywords ; @@ -44,7 +44,7 @@ var common = require( './common' ), ( function testProperlyRetrievesAllKeywords() { assert.ok( - ( ( keywords['final'] === true ) + ( ( keywords['virtual'] === true ) && ( keywords['static'] === 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 // 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" ); var oddword = 'foobunny', diff --git a/test/test-util-prop-parse-keywords.js b/test/test-util-prop-parse-keywords.js index 35a77d6..19e25b1 100644 --- a/test/test-util-prop-parse-keywords.js +++ b/test/test-util-prop-parse-keywords.js @@ -131,7 +131,7 @@ var common = require( './common' ), 'const foo2': '', 'public private const foo3': '', - 'public static final method': function() {}, + 'public static virtual method': function() {}, // tricky tricky (lots of spaces) 'public const spaces': function() {}, @@ -144,7 +144,7 @@ var common = require( './common' ), foo2: { '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 }, }