Code Reuse

Components


Content Author

Emily Christiansen

 

Reviewed/Revised By

Matt Gifford

coldfumonkeh

A more modern alternative to cfinclude is to create components. Components in ColdFusion behave similarly to objects in many other programming languages. First, start by creating a new file, called Greeting.cfc. ColdFusion uses .cfc to denote files that are components.

Inside of Greeting.cfc, add a component block:

component {

}

Think of this component like a bucket of code that you can carry around. You can hand it to a friend, and she can execute it, or she can hand it to her friend, or give it back to you. She can put more code in the bucket, or read values from it too. Components have properties, both public and private, and methods (functions). Properties are like labels on your bucket. You can read the label, and you can change its value.

For now, let's add functions to the component.

component {

    public string function getFullName(
        required string firstName,
        required string lastName
    ){
        var fullName = arguments.firstName & ' ' & arguments.lastName;
        return fullName;
    }

    public string function getGreeting(
        required string firstName,
        required string lastName
    ){
        var fullName = getFullName( argumentCollection = arguments );
        var greeting = "Hello, " & fullName;

        return greeting;
    }

}

Suppose we wanted make different types of greetings. Right now, the getGreeting function only allows us to say "Hello". What we can do is create a baseGreeting property that will be available to all of the functions in the component.

component accessors="true" {

    property name="baseGreeting" type="string" default="Hello, ";

    public function init(
        string baseGreeting = variables.baseGreeting
    ){
        setBaseGreeting( arguments.baseGreeting );
        return this;
    }

    public string function getFullName(
        required string firstName,
        required string lastName
    ){
        var fullName = arguments.firstName & ' ' & arguments.lastName;
        return fullName;
    }

    public string function getGreeting(
        required string firstName,
        required string lastName,
        string baseGreeting = getBaseGreeting()
    ){
        var fullName = getFullName( argumentCollection = arguments );
        var greeting = arguments.baseGreeting & fullName;

        return greeting;
    }
}

What have we done here? We've added a baseGreeting string property to the component, setting the default value to our default greeting.

We've also added a new attribute to the component definition, accessors="true". This will allow the component to provide explicit accessor (getter) and mutator (setter) functions for the properties we define. This means we can call getBaseGreeting or setBaseGreeting to fetch or update the value without having to write specific functions within the component ourselves.

We've also added a new method at the top of the component, init. This is the constructor method which will set the baseGreeting value with whatever value we choose to send in, or it will use the property default value, before returning the component itself.

By defining the property with a default value, that variable is now available within the variables scope, and can only be accessed from within the Greeting component via that scope. We can call it from outside of the component by calling getBaseGreeting().

Let's look at how we can call this revised component and methods to obtain our greeting.

<cfscript>
objGreeting = createObject( 'component', 'path.to.component.Greeting' );

myGreeting = invoke( objGreeting, 'getGreeting', {
    firstName = 'Matt',
    lastName  = 'Gifford'
} );

writeOutput( myGreeting );
</cfscript>

This would result in the following response: Hello, Matt Gifford.

We can pass in an alternative greeting and receive a different output:

<cfscript>
objGreeting = createObject( 'component', 'path.to.component.Greeting' );

myGreeting = invoke( objGreeting, 'getGreeting', {
    firstName    = 'Matt',
    lastName     = 'Gifford',
    baseGreeting = 'Welcome, '
} );

writeOutput( myGreeting );
</cfscript>

This would result in the following response: Welcome, Matt Gifford.

First, we created a new instance of the Greeting component using the createObject method. CreateObject takes in two parameters:

  • type: The type of object you want ColdFusion to create. In the example above we are making a component, so we pass in the string "Component". As of ColdFusion 9, this is no longer a required parameter.
  • component-name: The path to the component. Unlike the template argument for cfinclude, this argument takes the path to the component with each directory separated by dots instead of slashes. Because of this, it is not necessary to add the .cfc extension on to your file name. If you do, it will interpret the above as file ".cfc" in the folder "Greeting".

We then used the invoke method to call a specific publicly acessible method from the component instance. To do so, we passed in the variable reference to the object, the name of the function within that component to call, and finally we passed in a struct or values that are arguments for the method.

The first thing you probably noticed about the above section is that it is an awful lot of code to do three things:

  1. Instantiate the object
  2. Invoke the function
  3. Display the result.

Fortunately, there is a way to simplify this process. Let's start by instantiating our object:

<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init()>

You may notice that we have called the init method without sending in any values. In this case, the default baseGreeting property value will be used. We can override this during instantiation should we wish by passing in a new value like so:

<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init( baseGreeting = 'Hola, ' )>

Now that we have the object, we can call the function:

<cfset myGreeting = objGreeting.getGreeting(
    firstName = "Matt",
    lastName  = "Gifford"
)>

Notice how we have switched to a dot notation, similar to our examples of helper functions above. Now we get the sense of the object (the Greeting) doing something (the function). This is far more readable and descriptive than the alternative.

Finally, we can output the results:

<cfoutput>
    #myGreeting#
</cfoutput>

Ultimately, our finished, refactored example looks like this:

<cfset objGreeting = CreateObject( "Component", "path.to.component.Greeting" ).init()>
<cfset myGreeting  = objGreeting.getGreeting( firstName = "Matt", lastName = "Gifford" )>
<cfoutput>
    #myGreeting#
</cfoutput>

Let's change how we pass in the first and last name values for the individual.

As mentioned, we can pass any type of property into a ColdFusion component, including other components.

Let's create a new component called Person.cfc that accepts the firstName and lastName values as required properties:

component accessors="true" {

    property name="firstName"   type="string";
    property name="lastName"    type="string";

    public Person function init(
        required string firstName,
        required string lastName
    ){
        setFirstName( arguments.firstName );
        setLastName( arguments.lastName );
        return this;
    }

    public string function getFullName(){
        return this.getFirstName() & ' ' & this.getLastName();
    }

}

Once again, we have utilised the explicit accessors for the component, and we are passing through the required properties into the init method when we construct a new instance of the object.

We have also moved the getFullName method into this new object so we can use it whenever we need to as it relates directly to the person, and not the greeting.

Let's now update the Greeting component to accept this new object as a required value:

component accessors="true" {

    property name="person"          type="Person";
    property name="baseGreeting"    type="string" default="Hello, ";

    public Greeting function init(
        required Person person,
        string baseGreeting = variables.baseGreeting
    ){
        setPerson( arguments.person );
        setBaseGreeting( arguments.baseGreeting );
        return this;
    }

    public string function getFullName(){
        return getPerson().getFullName();
    }

    public string function getGreeting(
        string baseGreeting = getBaseGreeting()
    ){
        var fullName = this.getFullName( argumentCollection = arguments );
        var greeting = arguments.baseGreeting & fullName;

        return greeting;
    }
}

Here we have added a new person property to the component, setting the type attribute to match the name of the object. We have also updated the init method to require this new object as an argument, and we are setting the propery value using the setPerson method.

We still have our original getFullName method within this Greeting component, but instead of concatenating the strings here we are sending that request through to the getFullName method within the injected Person object.

Our updated call to fetch the greeting would now look like this:

<cfscript>
    objPerson   = createObject( 'component', 'Person' ).init(
        firstName = 'Matt',
        lastName  = 'Gifford'
    );
    objGreeting = createObject( 'component', 'GreetingInjection' ).init( person = objPerson );
    writeOutput( objGreeting.getGreeting() );
</cfscript>

By doing this, we now have two components that can be reused throughout our application's code base, and more importantly they both be tested to ensure they work as expected as and when they are updated or refactored.

We can streamline this code a little more. Instead of using the built-in CreateObject function, we can instead create and instantiate the component in one hit using the new function:

<cfscript>
    objPerson   = new Person( firstName = 'Matt', lastName = 'Gifford' );
    objGreeting = new Greeting( person  = objPerson );
    writeOutput( objGreeting.getGreeting() );
</cfscript>

The results are the same, but by using the new function you are implicitly calling the init function as you also create a new instance of the object.

Conclusion

ColdFusion offers a wide variety of ways to accomplish code reuse. By leveraging these techniques, you will find your code to be less repetitive and more fun to write. Code reuse is not an end in and of itself; rather, it is a means to another end: maintainability. Having code that is broken up into concise, self-documenting parts enables you to read and interpret code much more quickly.