Sunday 2 May 2021

CFML: pseudo-constructor polymorphic inheritance expectations management

G'day:

Well there's a sequence of words I never expected to write down.

I just ran across something that I was reasonably surprised by when I first saw it. But having looked at it some more, I'm not sure. So I thought I'd ask the 3-4 people who actually read this blog to offer their insight.

Consider this traditional example of polymorphism in play:

// Base.cfc
component {

    function runMe(){
        doThings()
    }

    function doThings(){
        writeOutput("BaseApp doThings called")
    }
}

// Sub.cfc
component extends=Base {

    public function doThings(){
        super.doThings()
        writeOutput("SubApp doThings called")
    }
}

// from StandardInheritanceTest.cfc
it("A subclass will override a base class method", () => {
    o = new Sub()
    o.runMe()

    expect(o.stack).toBe([
        "Base doThings called",
        "Sub doThings called"
    ])
})

The test (which passes) confirms what we'd expect: when runMe calls doThings, Because it's being called on a Sub object, the reference to doThings is referring to Sub.doThings even though the call is in Base. Hopefully no surprises there.

But what about this example:

// Base.cfc
component {

    this.stack = []

    doThings()

    function doThings(){
        this.stack.append("Base doThings called")
    }
}

// Sub.cfc
component extends=Base {

    function doThings(){
        super.doThings()
        this.stack.append("Sub doThings called")
    }
}

// from PseudoConstructorInheritanceTest.cfc
it("A subclass will override a base class method", () => {
    o = new Sub()

    expect(o.stack).toBe([
        "Base doThings called",
        "Sub doThings called"
    ])
})

The difference here is the call to doThings is not done by the test, it's done within the pseudo-constructor of the Base component.

And in this case the test fails:

Expected [[Base doThings called, Sub doThings called]] but received [[Base doThings called]]

It would seem the pseudo-constructor code of a base-class is not aware it's being called from a sub-class. This doesn't seem right to me?

I'm running this code on Lucee, but I ran equivalent code on ColdFusion and the results were the same (so I guess that's something). And given the behaviour is the same on both I'm thinking this is more me not understanding something, rather than a bug. What do you think?

BTW whilst testing this I found out I can get the behaviour I actually want with a slight tweak to Sub.cfc. I changed from this:

component extends=Base {

    function doThings(){
        super.doThings()
        this.stack.append("Sub doThings called")
    }
}

To this:

component extends=Base {

    doThings()

    function doThings(){
        this.stack.append"Sub doThings called")
    }
}

IE: I replicate the way doThings is called in Base.cfc: from the pseudo-constructor. Both calls are made. For my purposes this will work fine. But I still do find it curious.

Or am I being daft?

Righto.

--
Adam