Saturday, 5 April 2008

Stricter OOP without AOP! Sort of ;)

I've been playing around with the idea of forcing my components not to add or remove variables from their 'variables' scope, other than when instantiated. Its something I'd like not to be possible and I'd like a ColdFusion error if it happens. I blogged my first effort at cracking it here:

Stricter OOP using AOP!

The biggest downside to this was complexity of implementation. While I don't mind hidden complexity, using AOP meant that I would have to explicitly declare that each of my objects was wanting to use this feature/'aspect' - the effort in doing so using ColdSpring far outweighed any benefit. This was a 'global aspect' that would be best placed in a global base class if possible.

So, how to wrap all of a component's methods automatically using a base class that it extended? The concept is similar to AOP but it would lack the hassle of having to apply AOP to my model. Of course, the hassle would be figuring out how to do it!

A very long and steep-learning-curved night later and I have a working version. It borrows from the concept used in ColdSpring's AOP implementation of creating a temporary file in order to create component methods on the fly (mixins). The use is slightly different however. The following is an attempt to explain the base class constructor as concicely as possible:

  1. Store copy of self in a state variable.

  2. Create temporary cfc file with methods of the same name as those in this component. The body of these methods have a single line that calls a 'CallMethod()' method, passing the method name and any args as arguments.

  3. Instantiate temporary cfc and delete the file.

  4. Overwrite the original component methods with those in the temporary component

  5. Removed methods used in this process from the component

The result is a component that appears unchanged (from the outside) but that has each of its methods replaced with a wrapper that invokes the samed named method on a copy of the original component.

Once this component has been written, implementation couldn't be much easier. Simply extend the component and call it's constructor from within the extended component and you're done.

Rather than have all that code be used specifically for monitoring a component's variables, I first created an uber-base class which for now I have called selfproxy. This does what I have described above but does nothing useful in the 'self proxied' methods. To do something useful (such as monitor the variables scope), I just extend the component and override its method interceptor.

This 'self proxying' mullarky lacks the precision and flexibility of AOP, but if you're painting with a broad brush it might be a useful thing.

A working example (tested on ColdFusion 8 running on Windows XP).

I'm just scatting on a theme here and would be interested to hear any constructive criticism ;)

Anyways, time for bed!

3 comments:

jmetcher said...

Sounds like you're having fun here!

Just thinking about how to avoid having to generate a separate wrapper for each original method.

With closures, instead of

this[meta.functions[i].name] = tempComponent.GetMethod(meta.functions[i].name);

you could have

this[meta.functions[i].name] = MethodInterceptor(meta.functions[i].name);

If a method can know the name it's been called with (don't know if this is possible), you wouldn't even need closures, you'd just say

this[meta.functions[i].name] = MethodInterceptor();


Jaime Metcher

Dom said...

Yeh, I looked for a way for a method to know the name its been called with, but it doesn't seem possible.

I've tried catching a forced error and examining the result but the method name in the result is always the name of the original method (not the new name it has been 'copied' to). Even if that did work it would be pretty horrid!

Also, having looked at some source code for various projects, it seems that this exact technique has been around for quite some time!

Definately fun learning tho ;)

Ben Nadel said...

Looks pretty cool :)