Monday 13 April 2015

Lucee 5 beta: final components and methods

G'day:
I guess I need to go back and have a decent look at abstract components and methods at some stage, after my abortive first attempt ("Lucee 5 beta: abstract components (abject fail now fixed)"). But in the mean time, I'll look at the "opposite": final components and methods.

If one flags a component or method as final, then the intent is that that component or method cannot be extended by a sub component (or a method in same). This is a common feature in OO languages, although was never initially implemented in CFML's OO implementation. I really question the merits of adding it to CFML now, really. Beyond the idea that "well it should have been there", it's kind of only relevant for people building APIs, which doesn't often happen in CFML, and I've always found usage of final to be a pain in the arse more than anything actually useful. My position is I should be able to extend any class I like: it's not for the author of a class to pre-decide how I might end up using it.



Anyway, Lucee has added this in. I guess it makes sense if they've added in abstract classes and methods (another questionable, but slightly more useful, addition IMO).

Here are the relevant examples:

// FinalComponent.cfc

final component {

    public function f(){
        echo("FinalComponent version<br>")
    }

}

// ExtendsFinalComponent.cfc
component extends=FinalComponent {

    public function f(){
        echo("ExtendsFinalComponent version<br>")
    }

}

// finalComponent.cfm

finalComponent = new FinalComponent()
finalComponent.f()
echo("<hr>")
try {
    extendsFinalComponent = new ExtendsFinalComponent()
    extendsFinalComponent.f()
} catch (any e){
    echo("#e.type#<br>#e.message#<br>[#e.detail#]<br>")
}


Here the FinalComponent is declared as final, so it cannot be extended. So the code that just uses it unto itself works fine, but as soon as we try to use a CFC which extends it: error. And here's the output:

FinalComponent version

application
you cannot extend the final component
[C:\scratch\blogExamples\lucee\5\cfcs\final\FinalComponent.cfc]
[]


Not the most polished of exception message there, but at least it works.

OK, so how about a final method? This is a method in a component which itself is not final, but the method is final:

// FinalMethod.cfc

component {

    final public function f(){
        echo("FinalMethod version<br>")
    }

}

// ExtendsFinalMethod.cfc
component extends=FinalMethod {

    public function f(){
        echo("ExtendsFinalMethod version<br>")
    }

}

// finalMethod.cfm

finalMethod = new FinalMethod()
finalMethod.f()
echo("<hr>")
try {
    extendsFinalMethod = new ExtendsFinalMethod()
    extendsFinalMethod.f()
} catch (any e){
    echo("#e.type#<br>#e.message#<br>[#e.detail#]<br>")
}


As one might expect: trying to use code that extends that final method results in an error.

FinalMethod version

application
the function [F] from component [C:\scratch\blogExamples\lucee\5\cfcs\final\ExtendsFinalMethod.cfc] tries to overwrite a final method with the same name from component [C:\scratch\blogExamples\lucee\5\cfcs\final\FinalMethod.cfc]
[]


So far so good. Except the term is "override", chaps, not "overwrite".

It's pretty easy to bypass the finality of a method though:

// BypassFinalMethod.cfc

component {

    final public function f(){
        echo("BypassFinalMethod version<br>")
    }

}

// bypassFinalMethod.cfm

bypassFinalMethod = new BypassFinalMethod()
bypassFinalMethod.f()
echo("<hr>")

function f(){
    echo("UDF version<br>")
}
bypassFinalMethod.f = f

bypassFinalMethod.f()


Here, I "override" the final method by simply replacing it with another function using method injection. And this works fine:

BypassFinalMethod version

UDF version


I could not immediately think of a way to bypass final at component level though.

I think this functionality needs its error messaging cleaned up a bit (it looks a bit amateurish), but it kinda works. Although I don't see the point. And it only kinda works.

So far the only feature of Lucee 5 I've encountered that is any good is static methods. But I still think this needs work too.

I'll try to revisit abstract functionality shortly too.

Oh, and I still need some responses for my survey, please ("CFML: A quick OO terminology survey" / direct link to survey).

Righto.

--
Adam