4

Given e.g. of the Pseudo Constructor in CFML:

component{

    // Pseudo Constructor start
    
    ... here comes some cfml scripting code..

    // Pseudo Constructor end

    function init(){
        return this;
    }

}

I already understand that the Pseudo Constructor in a .cfc component:

  • Is the cfml code inbetween the component's beginning and the function init().
  • That the Pseudo Constructor's code runs immediately on components creation, even before the instantiating constructor "function init()" is invoked.
  • The variables created in the Pseudo Constructor are also available to all other components functions (Lexical Scoping)
  • Even code inbetween some component functions are also refered as Pseudo Constructors by some experienced cfml devs.

Please consider that I'm not refering to the use of cfproperty or property here, but to any other code in the Pseudo Constructor.

Still, I've not seen a good example or use case in CFML to help me decide. Could any experienced OOP CFML developer elaborate an example to understand it better:

  • When to use a code in the Pseudo Constructor and not the "function init()"?
  • Why NOT use the code in the instantiating constructor "function init()", so that the variables can be accessed/referenced with "this" keyword in any other components functions?

Please give an example code in such a manner that may help me and others decide when to use one over the other in the future?

AndreasRu
  • 1,053
  • 8
  • 14
  • You need to specify a particular problem. The way the question is written to only thing that can be set is whichever code is the more readable, most reusable, and the most terse is most like the best. Then again, that could be said about just about anything. – James A Mohler Dec 17 '21 at 16:54
  • @JamesAMohler thanks for chiming in. I don’t have any particular problem. I set all needed vars in the init function and that works great. Why, when and for what reason should I use the pseudo constructor then? Also, this would break encapsulation principles for components functions because of lexical scoping. I can’t see any good reason using the pseudo constructors at the moment. – AndreasRu Dec 17 '21 at 19:50
  • 5
    IIRC, there were no formal rules about component's having an `init()` function, nor was it invoked automatically, in earlier versions of CF (pre-CF9 I think?). So placing initialization code outside of an `init()` function was often done to ensure it was always executed. That concern's less relevant now, but ... it was an issue at one point in time. – SOS Dec 17 '21 at 20:41
  • 1
    That makes some sense @SOS. Thanks. Only thing I came around with is, that you may need some default component variables, which you can hide from outside the component by using the pseudo constructor: A kind of private variables that would be inaccessible and hidden from outside. I still want to check if these are also accessible when the cfc get extended. – AndreasRu Dec 18 '21 at 11:07
  • 1
    @AndreasRu - Yeah, but both of those end up in the `variables` scope, so there's no reason they couldn't be set inside an `init()` function. Even when using an [Implicit Constructor](https://helpx.adobe.com/coldfusion/developing-applications/building-blocks-of-coldfusion-applications/building-and-using-coldfusion-components/implicit-constructor-for-cfc.html) to initialize specific properties dynamically, private variables could still be initialized inside a no-arg `init()` function. So I still can't think of a good reason not to use `init()` other than it being a carry-over from old versions. – SOS Dec 19 '21 at 06:19
  • ` if these are also accessible when the cfc get extended...` Not sure about Lucee, but in Adobe CF a cfc's variables scope is also accessible to components that extend it. – SOS Dec 19 '21 at 06:21

1 Answers1

3

After investigating a little deeper I came up with my own conclusion that I'd like to share with all interrested CFML developers. If any experienced OOP CFML developer has more precise information, I'd be really glad to know.

I've created a component named Cube.cfc that may answer the question for itself. My conclusion basically is that "pseudo constructors" allow to create some kind of "static" functions, because these fucntions can make use of these variables without instantiation of an object. Note that I didn't want to use the term "static" because these functions lacks of the naming attribute "static" (as far as I'm aware, only Lucee supports real "static" functions at the moment). Also, the example I'm showing only seems to work with createObject() and not the implicit constructors e.g. new Component(), because new Component() would instantiate the object immediately. But: using createObject and the pseudo constructor will at least allow to mimic static functions. Please note that my example component is just to be descriptive.

In the following example im using available functions that won't need any object instantiation. These functions can be used to retrieve some usefull informations that are not bound to any created/instantiated object.

Cube.cfc: a simple component to create cube objects


component displayname="Cube" accessors ="true" {
    // class properties
    property name="name" type="string";
    property name="model" type="string";
    property name="borderColor" type="string";
    property name="material" type="string";
    property name="dimension" type="struct";
    
    // pseudo constructor
    variables.models =[
        "model-a", 
        "model-b",
        "model-c"
    ];
    
    variables.modelNameMaterialMapping ={
        "model-a": "wood",
        "model-b": "steel",
        "model-c": "silver"
    };

    variables.modelNameDimensionsMapping ={
        "model-a": {"height": 100, "length": 100, "width": 100 },
        "model-b": {"height": 133, "length": 133, "width": 133 },
        "model-c": {"height": 85, "length": 85, "width": 85 }
    };

 
    public any function init( 
        string name,
        string borderColor,
        string model

    ){
        setName( arguments.name );
        setBorderColor( arguments.borderColor );
        setModel( arguments.model );
        setMaterial(  getMaterialByModelName( arguments.model ) );
        setDimension( getDimensionByModelName( arguments.model ) );
        return this;
    }

    //this function won't need any instantiating of an object because it uses variables of the pseudo constructor
    public string function getMaterialByModelName( string modelName  ){

        return modelNameMaterialMapping[ arguments.modelName ];

    }

     //this function won't need any instantiating of an object because it uses variables of the pseudo constructor
     public struct function getDimensionByModelName( string modelName  ){

        return modelNameDimensionsMapping[ arguments.modelName ];

    }

     //this function won't need any instantiating of an object
     public string function isValidModel( string model ){

        return variables.models.contains( arguments.model );

    }


 }

index.cfm:

<cfscript>

    CubeService = CreateObject("component","Cube"); 
    writeDump( CubeService );
    writeDump( GetMetaData( CubeService ) );

    modelsForChecking=[
        "model-a",
        "model-k",
        "model-c",
        "model-z"
    ];

    // loop through model information without having any object instantiated
    for( model in modelsForChecking){
        if( CubeService.isValidModel( model )){
            writeOutput("Cube ""#model#"" is valid.<br>");
            writeOutput( "Cube models ""#model#"" are made of ""#CubeService.getMaterialByModelName( model )#"" and a dimension of ""#CubeService.getDimensionByModelName( model ).width#x#CubeService.getDimensionByModelName( model ).length#x#CubeService.getDimensionByModelName( model ).height#""<br>");
        }else{
            writeOutput("Cube ""#model#"" is NOT a valid model.<br>");
        }
    }

    //intantiate a specific cube object with the name "CubeOne";
    writeOutput( "Instantiate an object with the component:<br>");
    CubeOne=CubeService.init("CubeOne", "white", "model-c" );
    
    // dump properties of the specific cube "CubeOne"
    writeDump( CubeOne );

    // get width with the accessor getter for property "dimension" for the cube named "CubeOne"
    writeOutput("""CubeOne"" has a width of #CubeOne.getDimension().width# <br>");

  
</cfscript>

If you run the above files you'll note that the functions:

  • getMaterialByModelName( "model-a" ),
  • getDimensionByModelName( "model-b"),
  • isValidModel( "model-z" )

don't need any instantiated object. They just retrieve some usefull information about the cube models without running any init() function.

That causes me to assume the following thumb rules:

  1. Use variables that are bound to a property of an instantiated object within the "init()" functions.

  2. Use variables in the 'Pseudo Constructor' whenever you need to use functions with those variables before having used the init() function.

Note that my component is just to be an descriptive example. If somebody comes up with more detailed information about the topic or I need to be corrected in my assumptions, I'd be glad to know.


IMPORTANT UPDATE: As @SOS thankfully commented, Adobe Coldfusion supports static functions since Coldfusion 2021. These "non-instantiated-object-related" functions can now be directly invoked with Component::staticFunctionName( args ) without using any preceeding CreateObject() nor the implicit constructor new Component()! As @SOS also commented, looks like "using static is probably the best approach for 2021+" in CFML because now both CFML engines Lucee and Coldfusion fully supports them.

For completeness I'm placing an adapted/rewritten version of my example code as a reference for 2021+:

Cube.cfc

component displayname="Cube" accessors ="true" {
    // class properties
    property name="name" type="string";
    property name="model" type="string";
    property name="borderColor" type="string";
    property name="material" type="string";
    property name="dimension" type="struct";
    
    // set static varibales
    static { 
        private models =[ "model-a",  "model-b", "model-c" ];
        private modelNameMaterialMapping ={
            "model-a": "wood",
            "model-b": "steel",
            "model-c": "silver"
        };
        private modelNameDimensionsMapping ={
            "model-a": {"height": 100, "length": 100, "width": 100 },
            "model-b": {"height": 133, "length": 133, "width": 133 },
            "model-c": {"height": 85, "length": 85, "width": 85 }
        };

    };

 
    public any function init( 
        string name,
        string borderColor,
        string model

    ){
        setName( arguments.name );
        setBorderColor( arguments.borderColor );
        setModel( arguments.model );
        setMaterial(  static.getMaterialByModelName( arguments.model ) );
        setDimension( static.getDimensionByModelName( arguments.model ) );
        return this;
    }

   
    public static string function getMaterialByModelName( string modelName  ){

        return static.modelNameMaterialMapping[ arguments.modelName ];

    }

    public static struct function getDimensionByModelName( string modelName  ){

        return static.modelNameDimensionsMapping[ arguments.modelName ];

    }

    public static string function isValidModel( string model ){

        return static.models.contains( arguments.model );

    }


 }

index.cfm

<cfscript>
 
    modelsForChecking=[
        "model-a",
        "model-k",
        "model-c",
        "model-z"
    ];

    // loop through model information without having any object instantiated by calling static functions
    for( model in modelsForChecking){
        if( Cube::isValidModel( model )){
            writeOutput("Cube ""#model#"" is valid.<br>");
            writeOutput( "Cube models ""#model#"" are made of ""#Cube::getMaterialByModelName( model )#"" and a dimension of ""#Cube::getDimensionByModelName( model ).width#x#Cube::getDimensionByModelName( model ).length#x#Cube::getDimensionByModelName( model ).height#""<br>");
        }else{
            writeOutput("Cube ""#model#"" is NOT a valid model.<br>");
        }
    }

    //intantiate a specific cube object with the name "CubeOne";
    writeOutput( "Instantiate an object with the component:<br>");
    CubeOne=new Cube("CubeOne", "white", "model-c" );
    
    // dump properties of the specific cube "CubeOne"
    writeDump( CubeOne );

    // get width with the accesso getter for property dimension for the cube named "CubeOne"
    writeOutput("""CubeOne"" has a width of #CubeOne.getDimension().width# <br>");
  
</cfscript>

For further reference, see:

Static functions for CFC in Lucee

Static functions for CFC in Adobe

CFML: static methods and properties - by Adam Camaron

AndreasRu
  • 1,053
  • 8
  • 14
  • 1
    Good write-up. Adobe (finally) added `static` support in 2021, yay! Since, as you said, the behavior is more in keeping with `static` methods (and only works with createObject), using `static` is probably the best approach for 2021+. Still, cool to delve into this one (: – SOS Dec 25 '21 at 00:40
  • @SOS: Thanks for pointing to the Adobe support, and yes, I also think that static methods is the best approach for +2021. I've adapted my answer to have it also as a reference for myself. Thanks! Really cool stuff these static functions. – AndreasRu Jan 02 '22 at 00:35