Today, I started playing around with the `abstract...
# adobe
t
Today, I started playing around with the
abstract
keyword that was added in cf2018. Before I go filing bugs, I want to know if what I'm doing is supposed to work.
So first, it looks like you can't have an abstract that extends a concrete component. I can live with that. I'm assuming that's on purpose, but if that's not the case let me know and I'll file a bug about it.
But the second thing is that this doesn't appear to work:
Copy code
abstract component accessors=true {
   property string aThing;

   function init() {
       setAThing("default");
       return this;
   }
}
It complains that "Variable SETATHING is undefined."
a
And your concrete class simply extends that one? And the test code? Best to not make us guess what your code is.
And same code just without the
abstract
qualifier works?
t
Fair points. Let me back up a little bit. I'm refactoring existing code. So I've got a long-existing class from 15 years ago that is essentially an abstract, but true abstracts didn't exist. As a result, it has concrete do-nothing implementations of methods. Original code is like this:
Copy code
/**
 * ServiceProvider.cfc
 */
component accessor=true {
   property string basePath;
   property string applicationArea;

   public ServiceProvider function init() {
       setBasePath("");
       setApplicationArea("");
       return this;
   }

   more methods
}
I'm implementing some new Service Providers that share a bunch of common code, so I decided to abstract it to a common component
Copy code
/**
 * AbstractDocumentProvider
 */
abstract component extends="ServiceProvider" {
  stuff in here
}

/**
 * ClientDocumentProvider
 */
component extends="AbstractDocumentProvider" {
   concrete implementation
}
When instantiating ClientDocumentProvider, with the above code, it complained that
abstract
was a syntax error in AbstractDocumentProvider.
So I switched ServiceProvider to
Copy code
/**
 * ServiceProvider.cfc
 */
abstract component accessor=true {
   property string basePath;
   property string applicationArea;

   public ServiceProvider function init() {
       setBasePath("");
       setApplicationArea("");
       return this;
   }

   more methods
}
No other changes. Just added the asbstract keyword.
And now I'm getting this error that setBasePath is undefined.
So to answer the second question, yes the code worked fine without the abstract qualifier.
s
In non-CF languages, your abstract base class would need to declare those methods (with no body) -- so I expect CF follows that pattern too.
An abstract class is supposed to declare all the methods that concrete classes (which extend it) are supposed to implement.
t
Yes, but they can also provide concrete implementations that are inherited, right? Otherwise, it'd just be the same thing as an interface.
s
Yes, an ABC can implement some of the methods (but not all).
Personally, I don't like ABCs since that tends to mean folks are using inheritance to reuse implementations -- I prefer interface + concrete class and keep the inheritance tree really shallow. I think the latest ACF supports default methods in interfaces now which really removes the need for most ABCs of old? (or am I imagining that)
a
And now I'm getting this error that setBasePath is undefined.
Depending on how accurate your code is compared to what you show us, this will be because you have
accessor=true
. Note it should be
accessorS
.
I have this, verbatim:
Copy code
// Base.cfc
component {}


// AbstractIntermediary.cfc
abstract component extends=Base accessors=true {

    property string someProp;

    function init() {
        setSomeProp("default")
    }
}


// SubImplementation.cfc
component extends=AbstractIntermediary {}


<cfscript>
// test.cfm
o = new SubImplementation()
writeDump(o.getSomeProp())
</cfscript>
Which seems like a reasonable SSCCE of what you are desscribing, and it works fine. On CF2021. How does it work for you?
Also works on CF2018.
t
was in a meeting, but about to try yours to see what's going on. fwiw, the
accessor
vs
accessors
issue was just when i typed it in here, and doesn't exist in the real code.
Works for me too. So trying to find the differences between this and my actual code that i didn't think mattered, but must really.
Okay, so this was closer to my original code. This version works, just like your original example. It's just moved things from AbstractIntermediary to Base.
Copy code
// Base.cfc
component accessors=true {
    property string someProp;

    function init() {
        setSomeProp("default");
    }
}


// AbstractIntermediary.cfc
abstract component extends=Base {}


// SubImplementation.cfc
component extends=AbstractIntermediary {}


<cfscript>
// test.cfm
o = new SubImplementation()
writeDump(o.getSomeProp())
</cfscript>
That's the error I was getting with 1 (that I incorrectly interpretted to mean that Base had to be
abstract
too.)
Copy code
/**
 * Base.cfc
 * @accessors true
 */
abstract component {
    property string someProp;

    function init() {
        setSomeProp("default");
    }
}


// AbstractIntermediary.cfc
import foo.*;
abstract component extends=Base {}


// SubImplementation.cfc
component extends=AbstractIntermediary {}


<cfscript>
// test.cfm
o = new SubImplementation()
writeDump(o.getSomeProp())
</cfscript>
And here's the final version --- I was actually setting accessors=true via the annotation syntax. And when you add abstract to that (because I guessed the wrong cause to error 1), then it stops generating accessors.
a
So the explicit
abstract
combined with annotation-based
accessors
is the issue?
t
seems to be. And also explicit abstract combined with import before the component declaration.
a
urgh.
OK, good repro for a ticket, I think. Good you sorted it out though!
Good exercise in troubleshooting and how to ask questions for some of the other participants around here, too.
t
thanks for the help.
that does bring up an interesting point though. If both are in annotations, then it also works.
so the problem is with max-and-match
a
either / both / mix should work IMO.
I mean stylistically I'd stick with one or the other, but syntactically it should be fine.
t
agreed. I was thinking of it as a keyword, and forgot I could do it as an annotation.
doing it as an annotation allows import to go before the component again too. (had a typo originally)
s
My feeling has always been that comments should never affect code semantics so annotations in comments are a terrible idea in my opinion (and I said so loudly at the time I believe).
t
oh, actually, setting abstract in an annotation isn't a thing. It "worked" because our minimal abstract component had no methods.
s
But
@accessors
is valid in a comment?
t
yeah
s
Ugh! 😞 Don't do that 🙂
Comments can provide documentation hints/tips but should never modify the behavior of the code. That's just such a horrible, terrible, bad idea 😕
a
yup I can remember the PR when they got added, and railed against it then too. For the same reasons.
s
That second one (about
import
) shows how "dumb" the cfscript parser is 😞 The first one... well, I have no sympathy for anyone using annotations in comments 🤣