By Peter Bell

Would You like a Dynamic "extends"?

Even when you favor composition over inheritance, there are still great reasons for using base classes in terms of keeping a tight, DRY code base. However, the one thing that is frustrating about inheritance in ColdFusion is that the extends attribute cannot be variablized (oh to be able to pass an "extends" property as part of the createObject method). Would you have any interest if I added support for dynamic "extends"?

So, where would this be a problem? Well, lets say you have a set of base classes shared between n-projects in a framework. Now lets assume you have a combination of objects (some at the framework level, some at the project level) that need to be able to extend those base classes. And finally imagine that you'd like the ability to overload the base classes for some projects but not others. So in project A UserObject extends BaseObject, but in project B, UserObject extends ProjectBaseObject which extends BaseObject. In addition, the UserObject may be in project B, but it may also be an object stored in the framework directory that is shared between different projects and needs to extend a different object depending on which project it is being called from.

I would like to be able to describe the "extends" logic in LightWire and to dynamically control extends in the same way I dynamically control injection of properties and dependencies.

Right now, the only solution I can think of is to ask LightWire to copy the beans into a temporary directory, Regex the appropriate dynamically agreed extends attribute into the and then to call that bean. In fact, to keep support for package security, you'd probably have to do something like gen them into the same directory as #BeanName#_temp.cfc or something otherwise all beans would be able to call package methods (as they'd have all been genned into the same directory). In fact, thinking about it, the ability to programmatically overload the directory you genned into might be cool - it would mean that you could take a UserObject which is custom to a project and a UserDAO that is shared between two, gen them both at runtime into the same directory and allow UserDAO.save() to have package (protected) security and still be callable from UserObject - cool :->

I know there are other ways of solving the problem of being able to overload shared base classes on a project by project basis, but for my proposed outcome this is the "least inelegant" solution I've come across to date. Obviously there would have to be both an application wide and bean specific control to switch this on so it's only used on applications and beans that need it.

This is pretty unexplored territory in CF (to the best of my knowledge). Apart from general grumbling when people first find out that extends can't be dynamic I haven't really heard anything about people playing with this (and I can get why - it's probably quite an edge case and you could argue against the whole approach).

So, anyone any opinions on this pro or con? Would anyone else be interested if such a feature made its way into LightWire? For anyone who likes the idea, any preferences on how it would work or the syntax it might use?

Any input gratefully received.

Comments
Peter,

At first glance, this seems like a batch mixin. I know you have good reasons for why you want this. I am trying to grasp the implications of your proposal.

If you copied the bean innards into a temporary directory and then added your dynamic extends piece, it would require a file read and a file write, or maybe two. This might be something you could get away with for singleton objects, I would be loath to use such a thing for transients.

Maybe you could extend a generic object and have the generic object composed of the proper pieces through your DI framework? Sort of a proxy for the extended object.

DW
# Posted By Dan Wilson | 4/11/07 10:49 PM
Unfortunately, this is why most frameworks require the base code to be installed in a "known" location (e.g. /reactor, /coldspring, etc.).

If you "had" to do this, you might be able do it using auto-generated code in "reactor"-type fashion. Let's say you have a base framework "B", and your customized code "C", which normally extends "B", but "B" could be anywhere.

To combat that let's say that there's a a factory "F", and set of generated classes, "G", such that "C" extends "G", which extends "B". You ask the factory for an object based on your custom class "C", and it tries to create it, and fails because the "G" class is missing or invalid. It then generates the "G" class file based on a configuration value so it points to the current location of "B", and tries to instantiate "C" again.

Since "C" extends "G", which now points to the correct location of "B", everything works. I think I'd prefer this type of intermediate class approach as opposed to attempt to correctly read, parse, and rewrite a user's cfc to "extend" properly.
# Posted By Michael Long | 4/12/07 8:43 AM
@Dan, Bear in mind that I could cache the generated files in some manner, so for a given project, I'd gen the files as required only if they didn't exist or weren't current. I could probably live with some kind of appreset requirement if you changed your code for it to regen (you have to do that if you're changing code in your singletons anyway), so the performance would be fine for transients as well.

I guess you're right that the other approach would be to dynamically build the base class using runtime mixins - that's actually a pretty interesting approach, so I need to go think about that for a while :->

@Michael, It isn't so much that the base code needs to be in a specific place, but that some of the base code must be smart enough to extend project specific base classes. Lets say the product object is different between each project, but the user object is the same for most projects. So, I keep the user object as part of the framework (I can overload it for the few projects that need a custom user object). BUT, for some of the projects, user should extend #project#.com.BaseObject and in others it should extend com.baseobject, and I want it to be smart enough to go look and see if there is a #project#.baseobject - if so, use it - else extend the framework base class. That's what I'm looking to do.
# Posted By Peter Bell | 4/12/07 9:28 AM
Peter,

Does this information actually change at runtime? If not, why not just have your generator generate the appropriate code?

The idea greatly interests me, but I don't know that I'd use it here (of course, since I don't know how your system works, it may be the case that it completely makes sense to do so).

I think you guys already covered the options pretty well - those are the only 2 ways I can think of to do it at runtime.
# Posted By Sam | 4/12/07 11:55 AM
Well, firstly I have a base class which does the heavy lifting and the rules could potentially be object specific. Secondly I'm finding that I like dynamic programming a lot more than code gen as I can make lots of changes without the overhead of the generator cycle.

To me only reasons to generate code are for higher levels of reuse (click a button to gen an entire site), for limitation sin language or for performance considerations (where there are many caching strategies - code gen just being one of them). So, for me gen is becoming as thin a layer as possible on top of a really rich dynamic framework. Seems to be working pretty well so far, will keep you posted!
# Posted By Peter Bell | 4/12/07 12:13 PM
"Secondly I'm finding that I like dynamic programming a lot more than code gen as I can make lots of changes without the overhead of the generator cycle."

Me too...

"So, for me gen is becoming as thin a layer as possible on top of a really rich dynamic framework."

That's what I've been calling "synthesis" as opposed to generation all this time.. I don't know if there is an official term for it, and if there is, I don't know if that is it...

I see the good point to having it. I didn't realize you were taking that route - I always thought you were just generating the files outright and not doing as much dynamic stuff as you could have been. =)
# Posted By Sam | 4/12/07 12:43 PM
I did for a while, but as I build my framework up, I'm letting more and more become dynamic, and then after load testing I'll no doubt start to gen more and more - although I may well just gen to a agic "bin" as with reactor most of the time as if you're genning for performance reasons, you're really just adding another intermediate "language" so there is no reason for humans to see that any more than for them to look at MSIL or java bytecode.
# Posted By Peter Bell | 4/12/07 12:56 PM
Hey Peter,

This might be woolgathering, but what about adding support for pseudo-interfaces instead? If I'm reading your use case right, making extends dynamic is like saying, "My object has this implementation". If you can end run extends, I think it would be more value to add "implements" instead. Project classes always extend their (Abstract) Base classes, but can also "implement" another class at the Base or Project level. Just wondering...
# Posted By Paul Marcotte | 4/12/07 2:05 PM
Hi Paul, Interesting, but not quite where I want to go. I really want to say "I extend a base object, but the base object I extend needs to be determined dynamically".

I could use composition, but then I'd have to write either a generic delegate or a set of delegate methods so suddenly my code isn't so dry as each business object would need to have a delegate method for each method in the base object. The whole reason I'm using base objects is so I can drop in a new mbase method and it is available to all of my business objects - makes major refactorings a breeze.

Make sense?
# Posted By Peter Bell | 4/12/07 2:28 PM
Thought I was barkin' up the wrong tree.

I see the app generation outpost across the gorge, but I haven't completely bridged the gap. ;)

Carry on....
# Posted By Paul Marcotte | 4/12/07 3:30 PM
I've run across a need for this - I work in an environment were the web roots are handled differently between development, test and production. Right or wrong that's the way it is.

In dev, there's one big web root i.e. wwwroot/bsgs, wwwroot/lights
to access via browser http://mySever.com/bsgs/ or http://mySever.com/lights/

In test, some of the sites have virtual hosts
to access the site http://bsgs.myServer.com

Using getContextRoot() you can figure out what the correct path to use in your extends attribute, except that this value can't be a variable . . . :(

This is an older lists of posts, if something's come a long that makes this possible, I'd love to know.
# Posted By Trip | 4/28/08 3:02 PM
My work around is to create the same mappings/root structure for dev, staging and production. Only other solution I can think of would be a runtime pre-processor which would edit the class files or (more likely) put the extends as a variable and use ANT or another build script to edit the files before sending them to the appropriate production server.
# Posted By Peter Bell | 4/28/08 3:16 PM
Well I'm obviously pretty late to the game here. :) But seeing as Trip left his comments in April and here we are in July, I'll go ahead and throw my hat in.

Everyone... EVERYONE has said time and time again "it can't be dynamic because it's a compile-time directive/attribute"... and well... everyone is wrong.

And I have proof.

Here's the zip of a sample to show you how it's done: http://ontap.riaforge.org/blog/enclosures/test1%2Ezip

And here's my own blog article where I talked about the discovery:
http://ontap.riaforge.org/blog/index.cfm/2008/5/27...

I will say it's not 100% bulletproof. I was toying around with it a little earlier today trying to make it work with some ColdBox event handlers and it seemed oddly that it wasn't finding methods that should have been in them. My best guess (without having looked into it or tested it further, 'cause this was just a few hours ago), is that the missing methods were previously injected into the parent class or some other ancestor class and that method of injection causes those methods to be unseen when you use a cfinclude like this to create a dynamic extends. But that's the first time it's just flat out not worked for me.

If you're able to work this into Lightwire somehow, awesome! :) If not, then at least hopefully it may help Trip and a few others with their problems. :)
# Posted By ike | 7/11/08 10:08 PM
Oops! Copy-paste butchered the link to the archive.

http://ontap.riaforge.org/blog/enclosures/test1.zi...
# Posted By ike | 7/11/08 10:10 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.