<cfcomponent>
<!--- use cfscript so functions are hidden from metadata --->
<cfscript>
function mixin(type) {
var target = createObject("component",arguments.type);
structAppend(this,target);
structAppend(variables,target);
}
</cfscript>
</cfcomponent>
To test it, let's have a simple CFC with a couple of public methods we want to use (mixin):
<!--- use cfscript so functions are hidden from metadata --->
<cfscript>
function mixin(type) {
var target = createObject("component",arguments.type);
structAppend(this,target);
structAppend(variables,target);
}
</cfscript>
</cfcomponent>
<cfcomponent displayname="test.mixin.mixed">
<cffunction name="one" access="public">
<cfreturn "I'm one">
</cffunction>
<cffunction name="two" access="public">
<cfreturn " and I'm two!">
</cffunction>
</cfcomponent>
And here's the CFC where we use it:
<cffunction name="one" access="public">
<cfreturn "I'm one">
</cffunction>
<cffunction name="two" access="public">
<cfreturn " and I'm two!">
</cffunction>
</cfcomponent>
<cfcomponent displayname="test.mixin.plain>
<cfset mixin("test.mixin.mixed") />
<cffunction name="test" access="remote">
<cfreturn one() & two()>
</cffunction>
</cfcomponent>
I made that method remote so I could invoke it through the browser rather than have to write a CFML page just to test it:
http://localhost/test/mixin/plain.cfc?method=test (that will hit your own localhost setup!).
It mixes in just the public methods of the target class (so be warned that they can't depend on non-public methods in the target class!). It puts the methods in both this scope and variables scope so that name lookup works correctly (either just trust me that you need to do this or experiment for yourself!). It overwrites local methods with those from the target class (easy to change - structAppend() takes a third, optional, boolean argument that controls that).
Thoughts?<cfset mixin("test.mixin.mixed") />
<cffunction name="test" access="remote">
<cfreturn one() & two()>
</cffunction>
</cfcomponent>

18 responses so far ↓
1 Simon Horwith // Jan 12, 2006 at 2:49 PM
2 Mike Cohen // Jan 12, 2006 at 3:06 PM
3 Sean Corfield // Jan 12, 2006 at 3:16 PM
composedObject.someMethod()
whereas the mixin arranges for someMethod() to actually become part of your primary object at runtime. That means the API of the primary object changes to reflect the mixed in methods - and so client code can call those methods on your object without you needing to write proxy method stubs.
In fact exactly the same thing could be achieved through proxy generation / injection which is something I believe ColdSpring will be implementing. Well, almost exactly the same. Proxy generation mirrors the proxied API whereas mixins augment the API.
Make sense?
4 Spike // Jan 12, 2006 at 3:50 PM
I'd prefer to live with the explicit delegation in the vast majority of cases.
5 Mike Cohen // Jan 12, 2006 at 4:03 PM
6 Sean Corfield // Jan 12, 2006 at 4:53 PM
The mixin blends the public APIs of the two classes. You don't have to write test.mixin.plain.one() and have it delegate (to mixed.one()). It's essentially method injection.
If you only wanted to use the functionality of the mixin in order to implement your own methods then, yes, composition and internal delegation would be fine.
7 Joe Rinehart // Jan 12, 2006 at 5:57 PM
Sean, I started to write a reply about why I've gone with <cfinclude> instead of mixin() here, but as I wrote it, I realized I don't like either way very much, but just find <cfinclude> the lesser of two eveils. This led to a conclusion I wanted to share more widely, so I've posted it on my blog.
Simon, please feel free to chime in with why using <cfinclude> any further defeats the purpose of <cfcomponent> than using mixin().
8 Joe Rinehart // Jan 12, 2006 at 5:59 PM
9 Scott Barnes // Jan 12, 2006 at 6:50 PM
A large portion of the FLEX framework uses it to provide componentry with say, "event" dispatching for one.. without having to write the logic for each individual piece of the puzzle.
It can also, via runtime bolt on "logic" when needed, vs "always" having it in place.
On the whole, mix-ins smell at times like interfaces, yet not all the glory that comes with an interface.
I've not seen a use for them yet in Coldfusion that warrants them but i'm open minded to see some further contexts of how it may benefit us all.
Just be mindful, that i've personally found mix-ins hard to debug and test at times - yet - i'm not sure that could be the case with regards to CFMX, as its still quite procedural in its flow (Flash can thread nicely, where CFM has to wait for sub-logic to finish before it can continue onward..unless you use event gateways).
10 Matthew Lesko // Jan 13, 2006 at 5:36 AM
I thought one of the most interesting ideas presented there was the concept of "Traits" - primitive units of code that can be used as the building blocks of a class.
Bruce Eckel Forum Thread
www.artima.com/forums/flat.jsp?forum=106&thread=132988
Traits White Paper: www.iam.unibe.ch/~scg/Archive/Papers/Scha03aTraits.pdf
11 Jared Rypka-Hauer // Jan 13, 2006 at 8:03 AM
Given 3 classes: Person, Student, and Teacher
Person implements your garden variety behaviors like run(), walk(), and sleep(). But Student and Teacher both have to implement different versions of goToClass() (on heads to the main desk at the front, the other calls findASeat(), or in my case sneakInLate())
If you have a running Person object, you can call:
createObject("component","Student").initialize(Person)
This will inject the Student version of the appropriate behaviors, as well as any other Student-specific behaviors, into Person, which from then on is considered a Student. Since it walks, sounds, and swims like a Student, we can be all duck-typish about it and consider it a Student.
The only thing that it doesn't cover is the lack of restrictions on CFCs, so you can't force CF to guarantee that your Student really is a Student other than by checking for signatures in your own code to verify the type of Person your dealing with.
HTH...
J
12 Patrick McElhaney // Jan 13, 2006 at 10:03 AM
I'm shocked at how easily you bent on the "don't mess with component.cfc" rule.
The cfinclude method smells just fine to me. It can be dropped into any existing CFC. And it shouldn't be that hard to figure out what it does, even if you've never heard of mixins. There are no caveats like only mixing in public methods. There's no third-party code that could be different (or missing) from one machine to the next.
One major difference I see is your mixin is "dynamic" -- it can be applied at runtime -- whereas the cfinclude mixin is "static." I'm all for dynamic typing. I'm not so sure about dynamic mixins.
Patrick
13 Jared Rypka-Hauer // Jan 13, 2006 at 2:32 PM
Mixins via include... limited, disencapsulated (I made a new word!), and potentially a support nightmare (injecting private methods is a scary, scary idea). I vastly prefer the idea of injecting live methods from one class into another class. You're not dependent on the CF server's parser to determine whether or not a class is allowed, you can do the whole thing from outside the class (a factory reads both classes and injects the one into the other), you're not dependent on calling cfinclude multiple times to achieve your ends (which simplifies the process in general, and allows you to handle all of this from outside the object in question)...
It just strikes me as a better solution overall...
Laterz!
14 Josh King // Jan 13, 2006 at 4:28 PM
It's clever, and I have to agree that using <cfinclude> inside a component has a funky smell, but changing component.cfc is just asking for trouble.
15 Sean Corfield // Jan 14, 2006 at 3:56 AM
Jared has offered a variant on mine, using a 'universal base class' (for mixins) which also smells (and which doesn't even blend the public interface as far as I can tell - haven't tried the code yet).
Hal previously offered a similar (and, IMO, more complex) approach.
The fact that all of these solutions smell in one way or another should tell us something about mixins. Even in languages with great support for them (e.g., C++) they tend to smell really badly and, for the most part, can always be implemented in a less smelly way...
Patrick, actually Joe's cfinclude *is* a dynamic mixin too - cfinclude is a runtime construct, not a static one (it dynamically adds the contents of the included file where it is encountered at runtime). It is not a source code include like C / C++.
16 Patrick McElhaney // Jan 14, 2006 at 7:06 AM
cfinclude many be a runtime construct, but the way it's used in Joe's example, it's more like a compiler directive. The cfinclude is only called once, when the CFC is created. So you can only mixin when the object is created. You can't add mixins to existing objects.
But don't take this as an endorsement of Joe's method. Joe's method doesn't scare me as much because it's easily done and undone. But I'm still skeptical about mixins in general.
I'm glad that we finally stopped prentending CF is Java. Let's not start pretending that it's Ruby. :)
Patrick
17 Sean Corfield // Jan 14, 2006 at 9:07 PM
[cfif someCondition]
[cfinclude template="#someVariable#"]
[/cfif]
At *runtime* the contents of the included file are executed, which may result in additions to 'variables' scope. Remember that a function declaration is *executed* - the result is the addition of a new *variable*, named for the function, that contains a reference to the compiled class file that represents the function (as an object).
18 Robin Hilliard // Jan 17, 2006 at 3:46 AM
Interesting comments here. Firstly Sean's implementation of mixins is a lot less convoluted than my original method - I was trying to share a single original mixin object that I could use as a sort of static scope for all the mixin's clients, and that made it all a bit too complex to inflict on others or indeed myself. After explaining how it worked to a guy at Gruden and having him say "why not just use includes" I kept asking that question myself.
I should explain at this point that my broader philosophy regarding CF was and continues to be RAD, RAD, RAD - after some herculean efforts with a framework to emulate interfaces in CF, automatic persistence etc and thinking "why am I not just using Java?" I decided that RAD was what CF was about (and also the only truly unassailable benefit of CF when I was out selling it as an Allaire/Macromedia Sales Engineer). The reality also is that for every Model-Glue/Mach-II CFer out there there are ten happy Fuseboxers and a hundred who think a framework is something you grow tomatoes on - and they need our (self-professing-architect-dudes) help.
So shoot me, I've been using plain cfincludes for a while as mixins, really to see if my natural revulsion for the idea actually manifests itself as a real disadvantage. So far it seems to be working rather well, although I have to wash my hands a bit more thoroughly after using them...
Part of my research has involved reading about and experimenting with Ruby. Mixins (modules) are a core part of the Ruby language - for instance the Enumerable module adds implementations of all the iterator methods (inject, collect etc) to any class with an each() method. This is real code reuse - and I don't see it introducing any particular headaches for Enumerable's users.
If there's any language out there vying for CF's all-important RAD crown then Ruby would have to be a front-runner, particularly with the success of the Rails project. We should be learning what we can from Ruby, evaluating it in the cool light of day and using the good bits. If it makes development, debugging and maintainance easier, it works for me, good OO or no.
Leave a Comment