Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with:

Generating ColdFusion Class Methods On The Fly In A Wrapper Class

By Ben Nadel on
Tags: ColdFusion

Yesterday, in my blog entry on programmatic configuration objects, Peter Bell pointed out some cool things you could do if your object instance data variables were private and accessible only via getters and setters. Of the things that he mentioned, one was the use of wrapper classes... at least, I think that's what he was getting at. In any regards, he got the machinery firing full blast. I was inspired.

The problem with a wrapper class is that it has to "Act" just like it's target object would (the object that it is wrapping). That means that if the target object has a method named "GetFirstName", the wrapper has to have a method called "GetFirstName" that returns the value of the method when called on the target object itself. Remember, we are not extending the target object (in which case we only override the desired functions), we are wrapping it. The problem with this is that it's a pain in the butt to have to define all the methods that are getting wrapped. Wouldn't it be cool to wrap an object and JUST define the methods that are actually being altered?

Something like this in Javascript would be quite easy. You would simply loop over the function in a object and then define function literals to call those functions. The problem here though, is that ColdFusion does not have function literals. You can't just define functions on the fly, they have to be compiled.

Not one to take defeat well, I went ahead and solved this pesky little problem. I have created an AbstractWrapper.cfc ColdFusion component that is designed to be extended. In the class that is extending, all you have to do is define the methods that are being overwritten; the AbstractWrapper class takes care of the rest.

Let's look at an example. I have created a simple Girl.cfc ColdFusion component:

  • <cfcomponent
  • displayname="Girl"
  • output="false"
  • hint="I am a girl.">
  •  
  •  
  • <!--- Run the pseudo constructor. --->
  • <cfscript>
  •  
  • // Define the instance data object.
  • VARIABLES.Instance = StructNew();
  •  
  • // Set up default data (private variables).
  • VARIABLES.Instance.FirstName = "";
  • VARIABLES.Instance.LastName = "";
  • VARIABLES.Instance.Hair = "";
  • VARIABLES.Instance.SexyFactor = 0;
  •  
  • </cfscript>
  •  
  •  
  • <cffunction name="Init" access="public" returntype="Girl" output="false"
  • hint="Returns an initialized Girl object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="FirstName" type="string" required="false" default="" />
  • <cfargument name="LastName" type="string" required="false" default="" />
  • <cfargument name="Hair" type="string" required="false" default="" />
  • <cfargument name="SexyFactor" type="numeric" required="false" default="0" />
  •  
  • <!--- Store arguments. --->
  • <cfset VARIABLES.Instance.FirstName = ARGUMENTS.FirstName />
  • <cfset VARIABLES.Instance.LastName = ARGUMENTS.LastName />
  • <cfset VARIABLES.Instance.Hair = ARGUMENTS.Hair />
  • <cfset VARIABLES.Instance.SexyFactor = ARGUMENTS.SexyFactor />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="GetFirstName" access="public" returntype="string" output="false"
  • hint="Gets the first name.">
  •  
  • <cfreturn VARIABLES.Instance.FirstName />
  • </cffunction>
  •  
  •  
  • <cffunction name="GetHair" access="public" returntype="string" output="false"
  • hint="Gets the hair color.">
  •  
  • <cfreturn VARIABLES.Instance.Hair />
  • </cffunction>
  •  
  •  
  • <cffunction name="GetLastName" access="public" returntype="string" output="false"
  • hint="Gets the last name.">
  •  
  • <cfreturn VARIABLES.Instance.LastName />
  • </cffunction>
  •  
  •  
  • <cffunction name="GetSexyFactor" access="public" returntype="numeric" output="false"
  • hint="Gets the sexy factor.">
  •  
  • <cfreturn VARIABLES.Instance.SexyFactor />
  • </cffunction>
  •  
  •  
  • <cffunction name="SetFirstName" access="public" returntype="void" output="false"
  • hint="Sets the first name.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="FirstName" type="string" required="true" />
  •  
  • <!--- Store the argument. --->
  • <cfset VARIABLES.Instance.FirstName = ARGUMENTS.FirstName />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction name="SetHair" access="public" returntype="void" output="false"
  • hint="Sets the hair color.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Hair" type="string" required="true" />
  •  
  • <!--- Store the argument. --->
  • <cfset VARIABLES.Instance.Hair = ARGUMENTS.Hair />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction name="SetLastName" access="public" returntype="void" output="false"
  • hint="Sets the last name.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="LastName" type="string" required="true" />
  •  
  • <!--- Store the argument. --->
  • <cfset VARIABLES.Instance.LastName = ARGUMENTS.LastName />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction name="SetSexyFactor" access="public" returntype="void" output="false"
  • hint="Sets the sexy factor.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="SexyFactor" type="numeric" required="true" />
  •  
  • <!--- Store the argument. --->
  • <cfset VARIABLES.Instance.SexyFactor = ARGUMENTS.SexyFactor />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, this Girl.cfc does nothing all that special. She has some private variables, FirstName, LastName, Hair, and SexyFactor that are all gettable via "getter" methods and all updatable via "setter" methods. When I run this code:

  • <!--- Create girl object with default values. --->
  • <cfset objGirl = CreateObject( "component", "Girl" ).Init(
  • FirstName = "Cindy",
  • LastName = "Chan",
  • Hair = "Black",
  • SexyFactor = 8
  • ) />
  •  
  • <!--- Check values. --->
  • #objGirl.GetFirstName()#
  • #objGirl.GetLastName()#
  • #objGirl.GetHair()#
  • #objGirl.GetSexyFactor()#

... I get exactly what you would expect:

Cindy
Chan
Black
8

Ok, now let's say that we want to create a wrapper class for the Girl.cfc called GirlFriend.cfc. The GirlFriend object is supposed to work exactly like the Girl object with the exception that the get and set methods for SexyFactor always return and send in 10 no matter what is actually passed to them. After all, she IS my GirlFriend, and in my eyes she's always a perfect 10! (awwwww).

Remember though, I only care about the SexyFactor. I don't want to have to worry about the rest of the class methods; those should all work as they were working before. To do this, I create the GirlFriend.cfc to extend the AbstractWrapper.cfc and then JUST override the getter and setter methods for SexyFactor:

  • <cfcomponent
  • displayname="GirlFriend"
  • extends="AbstractWrapper"
  • output="false"
  • hint="I am the HOT GirlFriend.">
  •  
  •  
  • <cffunction
  • name="GetSexyFactor"
  • access="public"
  • returntype="numeric"
  • output="false"
  • hint="Returns the sexy factor appropriate for a GirlFriend.">
  •  
  • <cfreturn 10 />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="SetSexyFactor"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Sets the sexy factor appropriate for a GirlFriend.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="SexyFactor" type="numeric" required="true" />
  •  
  • <!--- Call on the target, but send 10. --->
  • <cfset VARIABLES.Instance.Target.SetSexyFactor( 10 ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, when I attempt to get the SexyFactor from the GirlFriend, it always returns 10. When I attempt to set the SexyFactor, the GirlFriend turns around and sends "10" to the object VARIABLES.Instance.Target. Just assume that the Target object is a reference to the original objGirl ColdFusion Component instance. Also notice that the GirlFriend.cfc extends the AbstractWrapper class.

Let's create the GirlFriend object and pass in the objGirl (which we created previously) as the target object to be wrapped:

  • <!--- Create girlfriend based on girl. --->
  • <cfset objGirlFriend = CreateObject(
  • "component",
  • "GirlFriend"
  • ).Init(
  • objGirl
  • ) />

Now, let's try and set some values and then get them to see what happens. Be sure to notice that while we ONLY DEFINED the getter/setter for SexyFactor, we are calling all the Girl.cfc getters/setters on the wrapper class (GirlFriend):

  • <!--- Set values. --->
  • <cfset objGirlFriend.SetFirstName( "Ashley" ) />
  • <cfset objGirlFriend.SetLastName( "Thomas" ) />
  • <cfset objGirlFriend.SetHair( "Brown" ) />
  • <cfset objGirlFriend.SetSexyFactor( 9 ) />
  •  
  • <!--- Get values. --->
  • #objGirlFriend.GetFirstName()#
  • #objGirlFriend.GetLastName()#
  • #objGirlFriend.GetHair()#
  • #objGirlFriend.GetSexyFactor()#

This gives us the following output:

Ashley
Thomas
Brown
10

How cool is that? The FirstName, LastName, and Hair color all went through (and were returned) successfully. The SexyFactor, on the other hand was overridden by the GirlFriend.cfc method definitions. Notice that we sent in "9" as a SexyFactor but the GirlFriend.cfc return "10" when the SexyFactor was requested.

All we had to do in the wrapper class (GirlFriend.cfc) was define the methods that we want overridden. The rest of the methods were send directly to the target object (Girl.cfc) or so it seems. Ok, enough suspense, how did I do it? The trick is that the base wrapper, AbstractWrapper.cfc, actually DOES create ColdFusion class methods ON THE FLY and adds them to the extending class. These dynamic methods are defined to take all of their arguments and use them to call the target object's methods.

There is NOTHING PRETTY about this. It's probably not very fast and it's not elegant, but, by god, it works. Let's look at the AbstractWrapper.cfc:

  • <cfcomponent
  • displayname="AbstractWrapper"
  • output="false"
  • hint="I wrap around another object and provide through-methods.">
  •  
  •  
  • <!--- Run the pseudo constructor. --->
  • <cfscript>
  •  
  • // Define the instance data object.
  • VARIABLES.Instance = StructNew();
  •  
  • // Set up default data (private variables).
  • VARIABLES.Instance.Target = "";
  •  
  • </cfscript>
  •  
  •  
  • <cffunction name="Init" access="public" returntype="AbstractWrapper" output="false"
  • hint="Returns an initialized wrapper with an internal target object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Target" type="any" required="true" />
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Store the arguments. --->
  • <cfset VARIABLES.Instance.Target = ARGUMENTS.Target />
  •  
  • <!--- Get the meta data for the target object. --->
  • <cfset LOCAL.MetaData = GetMetaData( VARIABLES.Instance.Target ) />
  •  
  • <!--- Get the functions array. --->
  • <cfset LOCAL.Functions = LOCAL.MetaData.Functions />
  •  
  •  
  • <!--- Loop over the functions to add the "through-methods" to this object. --->
  • <cfloop index="LOCAL.FunctionIndex" from="1" to="#ArrayLen( LOCAL.Functions )#" step="1">
  •  
  • <!--- Get a pointer to the current function. --->
  • <cfset LOCAL.Function = LOCAL.Functions[ LOCAL.FunctionIndex ] />
  •  
  • <!--- Get a pointer to the function arguments. --->
  • <cfset LOCAL.Arguments = LOCAL.Function.Parameters />
  •  
  • <!---
  • Check to see if this function is NOT defined in this wrapper class.
  • If it is, then it is something that the wrapper is overriding. If it
  • is not, then it is something that we want to create as a through-method.
  • --->
  • <cfif NOT StructKeyExists( THIS, LOCAL.Function.Name )>
  •  
  • <!---
  • The method does not exist. We are not overriding it manually. Therefore,
  • we just need to create a through-method that calls this method on the
  • target object.
  • --->
  •  
  • <!--- Store the code for the method. --->
  • <cfsavecontent variable="LOCAL.Code">
  • <cfoutput>
  •  
  • <[cffunction
  • name="__#LOCAL.Function.Name#"
  • <cfif StructKeyExists( LOCAL.Function, "Access" )>access="#LOCAL.Function.Access#"</cfif>
  • <cfif StructKeyExists( LOCAL.Function, "ReturnType" )>returntype="#LOCAL.Function.ReturnType#"</cfif>
  • <cfif StructKeyExists( LOCAL.Function, "Hint" )>hint="#LOCAL.Function.Hint#"</cfif>
  • ]>
  •  
  •  
  • <!--- Loop over the arguments and output them. --->
  • <cfloop index="LOCAL.ArgumentIndex" from="1" to="#ArrayLen( LOCAL.Arguments )#" step="1">
  •  
  • <!--- Get a reference to the current argument. --->
  • <cfset LOCAL.Argument = LOCAL.Arguments[ LOCAL.ArgumentIndex ] />
  •  
  • <[cfargument
  • <cfif StructKeyExists( LOCAL.Argument, "Name" )>name="#LOCAL.Argument.Name#"</cfif>
  • <cfif StructKeyExists( LOCAL.Argument, "Type" )>type="#LOCAL.Argument.Type#"</cfif>
  • <cfif StructKeyExists( LOCAL.Argument, "Required" )>required="#LOCAL.Argument.Required#"</cfif>
  • <cfif StructKeyExists( LOCAL.Argument, "Default" )>default="#LOCAL.Argument.Default#"</cfif>
  • /]>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Define the local scope. --->
  • <[cfset var LOCAL = StructNew() /]>
  •  
  • <!--- Invoke this method on the target object. --->
  • <[cfinvoke
  • component="##VARIABLES.Instance.Target##"
  • method="#LOCAL.Function.Name#"
  • argumentcollection="##ARGUMENTS##"
  • returnvariable="LOCAL.ReturnValue"
  • /]>
  •  
  •  
  • <!---
  • Check to see if return value is defined. If it is not, then the method
  • returned void. If it is there, then return it.
  • --->
  • <[cfif StructKeyExists( LOCAL, "ReturnValue" )]>
  • <[cfreturn LOCAL.ReturnValue /]>
  • <[cfelse]>
  • <[cfreturn /]>
  • <[/cfif]>
  •  
  • <[/cffunction]>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!--- Get a temp file. --->
  • <cfset LOCAL.FilePath = GetTempFile(
  • GetDirectoryFromPath( GetCurrentTemplatePath() ),
  • "udf_"
  • ) />
  •  
  • <!--- Write the function code to the file. --->
  • <cffile
  • action="WRITE"
  • file="#LOCAL.FilePath#"
  • output="#LOCAL.Code.ReplaceAll( '<\[', '<' ).ReplaceAll( '\]>', '>' )#"
  • />
  •  
  • <!--- Include the new method definition. --->
  • <cfinclude
  • template="#GetFileFromPath( LOCAL.FilePath )#"
  • />
  •  
  • <!--- Now that we have included the new method definition, delete the file. --->
  • <cffile
  • action="DELETE"
  • file="#LOCAL.FilePath#"
  • />
  •  
  • <!---
  • The new method definition is brought in as a private method. We need to
  • get a reference to it as a public method.
  • --->
  • <cfset THIS[ LOCAL.Function.Name ] = VARIABLES[ "__" & LOCAL.Function.Name ] />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the Init() method of the AbstractWrapper.cfc takes the object that it is going to wrap around. It stores this object reference in the private variable VARIABLES.Instance.Target. Then, it loops over all the methods in the target class Meta Data and checks to see if those methods are being overridden (are defined in) the concrete wrapper class (in our case, GirlFriend.cfc). If they are, it does nothing with them as they are already defined. If the method is NOT defined, however, it adds it to the concrete class definition on the fly.

This is where it gets really exciting, and I hope Peter Bell is somewhere smiling at this mini-attempt at Application Generation... we are actually writing the CODE for class methods and then including them (ColdFusion Mixin style) into the concrete class. The complete definition of the method as well as the arguments that it accepts are defined based on the Meta Data for the target class methods. This means they should look, feel, and act just like the real thing!

The weird thing here is that we have to include the new method definitions WITHIN another method (Init()). Frankly, I am surprised this works. But, I suppose since methods are "First class" objects (what ever that means), the idea of nesting is not what it might traditionally mean.

If you try to define a nested method, ColdFusion will throw a syntax error. It doesn't like methods being defined in methods. However, it seems that once the method file (the temp file that we write) gets compiled, the method definition in it is now a compiled first-class method and parsing is no longer an issue. And hence, nesting of OBJECTS (no longer definitions) works fine.

When you do it this way, the method is defined as a PRIVATE method. That is why I define it starting with two underscores "__". Then, I just create a public variable with the proper name and point it to the private method. Since variables can point to methods, this works quite nicely.

Anyway, this was all just an experiment and a venture into automated code generation. I am not recommending this. Heck, this behavior might even be considered a bug... but, I hope it's not. The one final thing to consider though is that the object types will not be the same. You cannot pass in a GirlFriend.cfc instance to a method expecting a Girl.cfc instance. They do NOT have the same type. Perhaps people with more OOP skills know how to solve that one.




Reader Comments

Hi Ben,

As always, bunch of interesting ideas!

Firstly, for ease of communication, what you're describing is (if I'm understanding you right) usually referred to as a decorator pattern - adding addtional functionality to an existing object. I guess there are types of wrappers that are not decorators, but I think for most of what you're talking about I'd call it a decorator (why does it matter? just quicker communications with people familiar with the pattern description - like talking about MVC or a factory or a visitor).

Secondly, I think you need to describe the use cases a little better. The problem you are solving would (again in a rush, so apologies if I'm misreading or not getting this) be solved euqally by girffriend extending girl as the missing classes would just automatically be called against the parent. There are plenty of times/reasons not to use inheritence, but it'd be interesting to describe a use case where inheritence wouldn't be a good approach and why it wouldn't be a good approach (I'm sure there are plenty).

Those are the nit picks, now onto main comment.

Am indeed smiling - this is cool! You should check out Seans PoC closures code as he does the same in one of the closure methods. Only difference is that unlike you he leaves the files he creates lying around in the directory - very untidy. I was starting to write up some comments about his posting, but never got a chance to finish them.

I'm not a big fan of the fact that CF doesn't support running of functions created at runtime without this kind of hacking, but I'm really gl;ad to see more and more people using this approach to generating code on the fly as I think it is extremely cool.

Bear in mind as with all mixins, your code is seriously not self documenting so for your 1 million LoC oddessy with 50 developers for 2 years this may not be the best approach, but there again, if you get good enough at metaprogramming, maybe you coudl replace the funcitonality on your own in 3 months, moving all of the rest of the "programming" into metadata that non-technical users can manage instead!

Reply to this Comment

Hey Ben,

As for the typing thing, that is where interfaces would be cool - that or making girlfriend extend girl which would also work (although you often need an object to be able to portray multiple types, so you need either multiple inheritance or interfaces). There are also some patterns that I believe you can use to solve the problem indirectly.

For me it isn't as issue. I am deep in the dark side and almost never type my objects as almost everything is dynamic and I almost never know what type I expect until runtime for a given piece of code!

Reply to this Comment

No way!!! I actually just did the same thing just the other day - almost literally - I like your implementation more than mine!!! I had never thought about writing code dynamically and creating functions on the fly until I saw Sean Corfield's Closures library. It's such a great concept!

Using this kind of thing on transient classes it also cool to think about. My original intentions were just to reduce the amount of typing needed in SERVICE classes. Service functions seem to be an exact copy of the gateway or DAO functions that just pass data through to the gateway or DAO, so I figured, why not create those passthrough/proxy methods on the fly and save myself the typing!!

I was actually going to start a project on RIAForge with this as my first offering. I was going to use the project name "OOUtils" that would contain cool classes/functions for doing dynamic OO wizardry like this.

I was going to name this particular piece "FunkyProxy" because it really just generates proxy functions. Anyway, I like the name, but since you got your stuff out to market first, I'm not going to bother creating my project since I don't want to look like a "taker" But hey, if you like the name "FunkyProxy" go ahead and use it.

Here are some other ideas I had about this sort of thing that I think would be cool to address.
A.) In my implementation I actually didn't EXTEND a wrapper class, I made "FunkyProxy" a separate class itself that took 2 objects as parameters. I haven't analyzed the 2 approaches to see which is more flexible or easy, but that's worth looking at

B.) I don't even know how to explain this, so here's an example
Base Method
doSomething(arg1, arg2, arg3)

Make it so the "Proxy" function automatically specifieds a pre-specified value any of the args, i.e. it generates a proxy function that looks like:

doSomething(arg2, arg3) {
return wrappedObj.doSomething(arg1=this.ID, arg2=arguments.arg2, .....)
}

C.) Be able to alias the function you're creating a function for so you have something like:

// in dynamically created class
doSomething(arg1) {
wrappedObj.doSomethingElse(arg1);
}

D. Cache the generated functions using the CLASS NAME as a key so that you don't have to do file operations each time you want to mix in methods from one CFC into another.

Actually, I'll email you a copy of the implementation I was hacking at if you're interested so you can see some of the stuff I did. I did the aliasing already. It's definitely not production ready yet, it was really a proof of concept I was playing with.

If you'd be interested in collaborating on this set of functionality and making it super fully featured and easy to use and put it on RIAForge, I'd be all in.

Reply to this Comment

Pete,

It's funny you mention Sean's code on Closures... I had seen that a while back and had no idea what he was talking about :) Even looking at it right now in Homesite and it's more than I can really understand. From the looks of it, he is doing way more stuff that I am. But cool that other people have explored this as well.

As far as use cases.... hmmmm. Well, we if are calling this the Decorator pattern, then I guess wherever one might use a Decorator patters. I have to admit, I know the decorator patters from what I have read (Head First Design Patterns), but don't quite remember why they used it. Something about drinks and different type (latte vs. non-fat-skim vs. carmel flavored) and how to not decorate would require too many classes.

Honestly though, I am waaaaay fresh to OOP so I don't really know what it is good to use anything at this point. Still just working it all out.

Reply to this Comment

Pete,

As far as this vs. "extending", you are correct. I guess, it I wanted to create a wrapper called "BleachedBlonde" that always returned "Blonde" as the hair color. Could wrap that inside the GirlFriend object... probably would be a super pain using extending.

But honestly, I am scraping the barrel here.

Reply to this Comment

Kurt,

I am not laying claim to anything here :) As Peter Bell points out, Sean Corfield has already done something like this. I guess the difference is that mine runs off of meta data and his... well frankly, I couldn't quite wrap my head around what his was doing, what with all that binding() and do() stuff. A bit too upper level for me. In fact, it looks like your idea more lines up with his than mine (what with the caching and all).

Feel free to take anything you want from my example and put it in your OOP utility thing. Heck, it's open source anyway right?

As for ganging up and writing some stuff, I am all for it! I love this stuff. I am a fan of ColdFusion. It looks like you had a better of idea of where you wanted to go with this package so I think it would be I contributing to your project. Please let me know what I can do.

Reply to this Comment

I guess fundamentally you'd use this for cases when you needed composition instead of inheritance but didn't want to write the simple delegate methods.

For example, if a UserDAO IS a BaseDAO, you can have BaseDAO.save() BaseDAO.delete(), BaseDAO.get(), etc. and the methods are available to UserDAO so you don't have to write anything in UserDAO to cll UserDAO.save().

That is inheritance 101.

Often you favor composition over inheritance. Maybe you want to add notification.sendmessage() to an object, you might inject notification into the object and then write a simple delegate so myobject.sendmessage() would call the composed notification.sendmessage().

THAT's what you're written - a delegate generator!

Reply to this Comment

This is exactly the technique used in Mark Mandel's Transfer (http://transfer.riaforge.org/). It's worth checking out as an example of this approach pushed waay further than I can easily follow.

Use cases - delegation and mixins are basically ways to achieve multiple implementation "inheritance", no?

Decorators - IIRC, in the GoF decorator pattern the decorator and decorated are both subclasses of the same superclass - which removes the need to delegate any methods implemented in the superclass. The motivation being that the decorator can decorate any of it's sibling subclasses. If you changed GirlFriend to SignificantOther, added a Boy class and then made Boy, Girl and SignificantOther subclasses of Person I think that would be getting close. SignificantOther could decorate either Boy or Girl, and saves you having both BoyFriend and GirlFriend subclasses of Boy and Girl respectively. But with only Girl, there's no win and you might as well just subclass it.

Sorry if that's dense (or wrong!). Hard to saythis without diagrams.

Jaime

Reply to this Comment

Jaime,

Thanks for the link. Mark is a really smart dude and I am sure what ever he is doing is like 100 times better than what I am merely experimenting with. These are just explorations so it wouldn't suprise me that it is like something that someone else has already done.

I understand what you are explaining. I will try and get hold of Mark's code and take a look. Thanks for the link.

Reply to this Comment

Hi Ben, nice work. I realise this post is over a year old but I have just done what you did back then (write something like this to discover people are doing similar things everywhere)!

My implementation is a little different. I wanted a base class that does the wrapping on itself. This way, any class that extends the base class has all its methods wrapped by a wrapper defined in the base.

My blog explains why I wanted to do this; I realise I'm behind the times but I'm rather excited by it all :p

http://fusion.dominicwatson.co.uk/2008/04/stricter-oop-without-aop-sort-of.html

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.