Foundations
Mixins
Writing software that's more flexible and maintainable
Mar. 11, 2006 02:00 PM
The only thing needed is to include Teacher.cfm and write an init method - a sort of constructor for our CFC.
Our StudentTeacher.cfc is similar:
<cfcomponent displayname="StudentTeacher" extends="Student">
<cfinclude template="Teacher.cfm" />
<cffunction name="init" access="public" output="false">
<cfargument name="average" required="true" />
<cfargument name="name" required="true" />
<cfargument name="test" required="false" default=null />
<cfset super.init(arguments.average, arguments.name) />
<cfset set('test', arguments.test) />
<cfreturn this />
</cffunction>
</cfcomponent>
Without extending or implementing anything, we have the one authoritative, unambiguous representation of what a teacher is.
That's not to say, though, that we can't extend classes. In fact, StudentTeacher extends Student.cfc:
<cfcomponent displayname="Student" extends="BaseComponent">
<cfinclude template="Student.cfm" />
<cffunction name="init" access="public" output="false">
<cfargument name="average" required="true" />
<cfargument name="name" required="true" />
<cfset set('average', arguments.average) />
<cfset set('name', arguments.name) />
<cfreturn this />
</cffunction>
</cfcomponent>
Student is similar to Teacher and includes the one authoritative, unambiguous representation of what a student is: Student.cfm:
<cfset variables.instance.average = 0 />
<cfset variables.instance.name = null />
<cffunction name="study" access="public" output="false">
<cfreturn "Yes, yes, I'm studying..." />
</cffunction>
<cffunction name="takeTest" access="public" output="false">
<cfargument name="test" required="true" />
<cfif arguments.test.get('percentOfGrade') LT 10 AND get('average') GTE 100>
<cfreturn skipTest() />
<cfelse>
<cfreturn workHardOnTest(arguments.test) />
</cfif>
</cffunction>
<cffunction name="skipTest" access="private" output="false">
<cftrace type="Information" text="I think I'll just skip this test.
I've already got a #get('average')#" />
</cffunction>
<cffunction name="workHardOnTest" access="private" output="false">
<cfargument name="test" required="true" />
<cftrace type="Information" text="I'm taking the test: #arguments.test.get('name')#" />
</cffunction>
We should make sure all this works:
<cfset anne = CreateObject('component', 'Student').init(94, 'Anne Baker') />
<cfset bob = CreateObject('component', 'Student').init(82, 'Bob Carter') />
<cfset carla = CreateObject('component', 'StudentTeacher').init(100, 'Carla Davis') />
<cfset test = CreateObject('component', 'Test').init('OO Development with ColdFusion', 8) />
<!--- have the Teacher do its thing--->
<cfset teacher = CreateObject('component', 'Teacher').init(test) />
<cfset teacher.addStudent(anne) />
<cfset teacher.addStudent(bob) />
<cfset teacher.addStudent(carla) />
<cfoutput>
#teacher.assignHomework()#<br>
#teacher.giveTest()#
</cfoutput>
<!--- now let's have the StudentTeacher --->
<cfset carla.set('test', test) />
<cfset carla.addStudent(anne) />
<cfset carla.addStudent(bob) />
<cfoutput>
#carla.assignHomework()#<br>
#carla.giveTest()#
</cfoutput>
Though it's not important for this discussion, I've included the code for Test.cfc for completeness. It can be found in Listing 1.
Object-Based Mixins
Class-based mixins work well when we want to build mixins into our design. As their name suggests, class-based mixins affect all instances of the class. Sometimes, though, that's not what we want. We may have objects in memory that we need to make structural changes to; we may need to affect some, but not all, members of a class; or we may be using code that we have no ownership of and no ability to alter.
In any of these cases (and more), a class-based mixin is not appropriate. Object-based mixins provide the ultimate in flexibility - even during runtime. Object-based mixins let us "inject" all methods and variables from one object (not class) into another object.
About Hal HelmsHal Helms is a well-known speaker/writer/strategist on software development issues. His monthly column in CFDJ contains his Musings on Software Development and he has written and contributed to several books. Hal holds training sessions on Java, ColdFusion, and software development processes. He authors a popular monthly newsletter series. For more information, contact him at hal@halhelms.com or see his website, www.halhelms.com.