Robin Hilliard showed a mixin technique ages ago (October 2004) that used a class / method to inject the mixed-in methods and that appeals more to me.
So I figured I'd develop my own solution and offer that. I've left component.cfc alone on my systems for a long, long time, on the grounds that it's a "system component" and shouldn't be messed with (and, hey, it might get overwritten in an upgrade). However, this seemed like a really good use for modifying that "universal base class"...
Edit WEB-INF/cftags/component.cfc so it looks like this:
<!--- 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>
<cffunction name="one" access="public">
<cfreturn "I'm one">
</cffunction>
<cffunction name="two" access="public">
<cfreturn " and I'm two!">
</cffunction>
</cfcomponent>
<cfset mixin("test.mixin.mixed") />
<cffunction name="test" access="remote">
<cfreturn one() & two()>
</cffunction>
</cfcomponent>
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?
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?
I'd prefer to live with the explicit delegation in the vast majority of cases.
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.
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().
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).
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
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
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
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!
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.
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++.
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
[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).
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.


