Inheritance
Just as we can use the word "my " to refer to a CFC's THIS scope ("my ID is 123 and my First Name is Fred…"), in inheritance , we can think of the words is a. An actor is a person. A cat is a mammal. A novel is a book. In these cases, actor, cat , and novel are "children" of person, mammal , and book . Some parents can exist by themselves; there can be a person who is not an actor. Some other parents, though, are abstract; you will never see a creature in the zoo called, simply, "mammal." Rather, the parent is intended as more of a handy template upon which more specific things can be based. Although two different mammals share some characteristics by virtue of being mammals (they are warm-blooded and give birth to live young, for example), they can be made different either by having unique properties and behaviors, or by modifying some aspect of the parent (consider the egg-laying platypus!).Our list of known facts in the movie studio application included the statements that actors and directors are both types of people with some common properties and some unique. So by being types of "people," you could create a component to represent a "person" and have each of these variants inherit from it.Listing 19.11 is the basic person component. It has a first name and last name (stored in the THIS scope) and has one function that "shows" the person by outputting the first and last name.
Listing 19.11. person.cfcThe Basic person Component
A component inherits from a parent component with the EXTENDS attribute of the <cfcomponent> tag. The value of the attribute is the name of the component upon which the new component should be based. Thus, a director component could consist of nothing more than this code:
<!---
person.cfc
Component that contains a "person", firstname and lastname only
Modified by Ken Fricklas (kenf@fricklas.com)
Modified: 2/15/2005
Code from Listing 19.11
--->
<cfcomponent displayName="Person" hint="Parent Component - Person">
<cfparam name="THIS.firstName" default="John">
<cfparam name="THIS.lastName" default="Doe">
<cffunction name="showPerson" output="true">
<B>#THIS.firstName# #THIS.lastName#</B>
</cffunction>
</cfcomponent>
Now, the director is an exact copy of the person and has "inherited" all the properties and methods of its parent. A CFML page, then, could create an instance of the director and invoke the methods of the person component as though they were part of the director component:
<cfcomponent displayName="Movie Director" extends="person">
</cfcomponent>
Just because the parent says something doesn't mean the child is stuck with it (they wish, anyway). In components, at least, that is truly the case. The component can "override" parts of the parent component. If we added code to the director component to set the THIS scope with variables of the same name, the director, because it's the one being invoked, will take precedence. So the director component is now coded like this:
<cfobject component="director" name="myDirector">
<cfoutput>#myDirector.showPerson()#</cfoutput>
and when invoked from the CFML page will output "Jim Jarofmush" instead of "John Doe" as it did before. The THIS scope assignments made in the child overrode those of the parent. Likewise, adding a showPerson function to the director component will override the showPerson function from the parent:
<<cfcomponent displayName="Movie Director" extends="person">
<cfset THIS.firstName = "Jim">
<cfset THIS.lastName = "Jarofmush">
</cfcomponent>
In addition to the child's being able to invoke functions that are really part of the parent component (and overriding them, if desired), the parent can call functions that are part of the child by referencing them in the instance's THIS scope. Say we added a function called showDetail to the director component:
<cffunction name="showPerson" output="true">
<B>A swell director named #THIS.firstName# #THIS.lastName#</B>
</cffunction>
We could then modify the showPerson function in the parent to refer to the child's showDetail function by prefacing the name of the function to be called with the THIS scope. This code means, "Invoke the showDetail method on the instance of the component that I am currently in…":
<cfset THIS.credits = arrayNew(1)>
<cfset THIS.credits[1] = "The Phantom Dentist">
<cfset THIS.credits[2] = "Austin Showers">
<cfset THIS.credits[3] = "Men in Slacks II">
<cffunction name="showDetail" output="true">
Credits include:
<UL>
<cfloop index="i" from="1" to="#arrayLen(THIS.credits)#">
<LI>#THIS.credits[i]#</LI>
</cfloop>
</UL>
</cffunction>
In the calling template now, the showPerson function can take an optional Boolean argument which, if present, will invoke the showDetail method from the director component:
<cffunction name="showPerson" output="true">
<cfargument name="showDetail" required="false" type="boolean" default="false">
<B>#THIS.firstName# #THIS.lastName#</B>
<cfif showDetail>
<BR>#THIS.showDetail()#
</cfif>
</cffunction>
CAUTIONNotice in the preceding example that the method was invokedincluding the passing in of argumentsin function syntax. The showPerson method took one Boolean argument which we were able to pass in like so: showPerson(true). This is just another way of invoking component methods. Bear in mind, though, if you use this approach, order is important! The order in which you pass arguments with the function must match the order in which the <cfargument> tags are coded!The result of our showPerson function now will be a combination of showPerson from the person component and showDetail from the director component:
<cfobject component="director" name="myDirector">
<cfoutput>#myDirector.showPerson(true)#</cfoutput>
Jim JarofmushCredits include: The Phantom Dentist Austin Showers Men in Slacks II
This technique can be useful when multiple components are descendants of the same parent but require slightly different methods. Say we based an actor component on the same "person" parent using this code:
When the showPerson function is invoked, the appropriate showDetail function is used, depending on whether the component is an actor or director:
<cfcomponent displayName="Movie Actor" extends="person">
<cfset THIS.firstName = "Judi">
<cfset THIS.lastName = "Dents">
<cffunction name="showDetail" output="true">
Star of the hit <EM>The Importance of Being Sternest</EM>.
</cffunction>
</cfcomponent>
This code results in this output:
<!--- instantiate an actor and a director --->
<cfobject component="director" NAME="myDirector">
<cfobject component="actor" NAME="myActor">
<!--- demonstrate that showPerson calls different showDetail functions for each --->
<cfoutput>
Directed by:<br>
#myDirector.showPerson(true)#
<br>
Starring:<br>
#myActor.showPerson(true)#
</cfoutput>
Directed by:A swell director named Jim Jarofmush Starring:Judi Dents Star of the hit The Importance of Being Sternest.
Must you use inheritance in your ColdFusion applications? Certainly not. But it can be very useful in ways similar to other code-reuse techniques. Components can be built-in "branches," as in a family tree with chains of ancestral parent components, each providing base functionality to their children.Component packages can help with this type of organization, too, to make applications more easily maintained. In that case, the extend="..." attribute uses the same package path syntax as a <cfinvoke> tag. For example, to inherit from a component called "person" in the package myApp.components, the <cfcomponent> tag would be coded like this:
<cfcomponent extends="myApp.components.person">