Here's a question where I had a couple of answers, but I'm willing to bet we can have a good debate as to what would be preferred. I'll post the question, my answers, and then I'd love to see what other people think as well. First, the question from Don:
In one method of a cfc I need to know the name of the cfc. I know this seems strange but the cfc extends a superclass and I need to know which subclass I am in.Maybe an example will make this less muddy.
voAccount and voCustomer both extend voSuperclass
There is a method in the superclass in which I would like to know which subclass is executing the method.
I could create an instance variable when I run the init method and set this.componentName=foo but that would mean adding a parameter to the init method and while that is no big deal I was hoping to determine the name programatically without this step.
Any Suggestions?
So to start off, let's design a super simple model we can use for testing. Imagine a child component. This child component has one method, getGift(). Santa Claus uses it to determine what gift should be given to the child. Two child classes (of child - heh - sorry if that's confusing) extend this base: badchild and goodchild. Bad children will get coal. Good children will get a Red Ryder BB Gun. So given that getGift() is defined in child.cfc, how can we handle handing out the right gift? Here are some ideas!
In my first version, I suggested simply checking the metadata for the current component. As an example:
child.cfc
component {
public string function getGift() {
var md = getMetadata(this);
var myname = listLast(md.name, ".");
if(myname == "goodchild") return "Red Ryder BB Gun";
if(myname == "badchild") return "coal";
throw "Invalid child!";
}
}
As you can see, I examine the metadata for This, the current component, and simply look at the name component. This will contain, possibly, dot paths, so I just grab the end. I'm not going to show you goodchild and badchild since they both contain just 2 lines of code, with the critical line being: component extends="child" {
To test this, I wrote:
<cfset ray = new v1.goodchild()>
<cfset scott = new v1.badchild()>
<cfset raygift = ray.getGift()>
<cfset scottgift = scott.getGift()>
<cfoutput>
Ray gets: #raygift#.<br/>
Scott gets: #scottgift#.
</cfoutput>
The output is as you expect:
Ray gets: Red Ryder BB Gun.
Scott gets: coal.
Ok, so how about another way? In my second method, I tried using isInstanceOf. This is a function I've rarely used, but it works well here. Once again, here is child.cfc:
component {
public string function getGift() {
if(isInstanceOf(this, "goodchild")) return "Red Ryder BB Gun";
if(isInstanceOf(this, "badchild")) return "coal";
throw "Invalid child!";
}
}
As you can see - it checks the current component against a string type and if true, returns the right gift. To test this I wrote:
<cfset ray = new v2.goodchild()>
<cfset todd = new v2.badchild()>
<cfset raygift = ray.getGift()>
<cfset toddgift = todd.getGift()>
<cfoutput>
Ray gets: #raygift#.<br/>
Todd gets: #toddgift#.
</cfoutput>
And got...
Ray gets: Red Ryder BB Gun.
Todd gets: coal.
So far so good. How about a third way? I know that Don said he wanted to do things programatically and he specifically said he didn't want to use a hard coded value in the init method. However - what about writing a getGift method in the children that than tie to the paren'ts getGift? So for example, consider this version of goodchild.cfc:
component extends="child" {
public string function getGift() {
return super.getGift("good");
}
}
Notice how it still makes use of the getGift from the base class, but it handles passing along a type of child. The badchild has this code:
component extends="child" {
public string function getGift() {
return super.getGift("bad");
}
}
And finally, child.cfc is:
component {
public string function getGift(string type) {
if(arguments.type=="good") return "Red Ryder BB Gun";
if(arguments.type=="bad") return "coal";
throw "Invalid child!";
}
}
Ok, so which is "right"? Honestly, I can't say. To me, the third method feels closest to proper, if its even fair to say proper. The base class no longer cares about the type - instead of the CFCs who extend it worry about it. The base class method focuses on returning the right gift based on the type of child. With the third method I also feel like I have more control. So imagine a naughtychild.cfc - a child who isn't bad, just naughty. I could possibly write code to say, based on the economy, gift the naughty child a good gift anyway. Yeah I'm kinda pulling that example out of my rear, but you get the idea.
So thoughts? I'll remind Don, and others, to keep in mind that there is no One True Way, and I know my commenters won't suggest that (since we've got a smart bunch here).
I've included my test code as an attachment to this entry.
Archived Comments
Thanks Ray for demonstrating this, but I have to say that, in *most* cases the need for a superclass to know about its subclasses indicates a flaw in the design. You would have to change the method every time you add a new subclass. The superclass thus has an extra reason to change besides possible changes in its own functionality. If you can avoid that, you will probably be better off in the long run.
Oops! Sorry! Note to self to read the post better before reacting. Your third method is obviously the best one.
Thanks Ray.
I think I like a modification of the third way. :-) i.e. use the getMetaData in the subclass to populate the name and pass it to the superclass method. That way you can use a snippet template when creating the subclass without hard coding it anywhere.
This is an inherited application with hundreds of subclasses.
In the process of migration several of the attributes in the subclasses have changed to accomated new middleware. The superclass uses implicit getters and setters. The existing ui, of course, uses the old attribute names so it has getOldName(),
In order to avoid (temporarily) refactoring the ui the superclass does a lookup to find the correct mapping to the new name (ie returns getNewName() ) so the application doesn't break and block other developers. The mapping is dependent on which child is calling the superclass. The superclass then logs which piece of ui code made the call aiding the developers working on the ui. There is no intent to leave the mapping process on in the production version, although if the middleware changed the schema and required a new name later you could leave the mapping in place, change the attribute in the subclass and never have the ui care.
Actually, if I modify the init() method of the superclass to set the name I don't need to modify any subclasses.
init() {
....
md =getMetadata(this);
this.componentName=md.name;
....
The superclass can then using this.componentName in its lookup for the mapping and the hundreds of subclasses can remain unchanged.
I've run into some discrepancies with the getMetaData() function and the returned .name property. On our Windows/IIS dev server this returns the full dot-notation path. However, on our Linux/CentOS/Apache dev server, the name is just the filename (missing the extension) as if the web-root was actually the local folder. Has anyone run into this issue? I'm assuming there is a server config that's missing. However, in the httpd.conf file defines the document root and using createObject() and using the normal dot-notation for the full path from the web root does work. Its odd the web-root seems to be in use in some cases, but in other it isn't. Any ideas how I can ensure getMetaData() is consistent across Linux and Windows?
RE: my previous post, looks like this is a bug with 9.0, the update to 9.0.1 looks like it fixes the getMetaData() issue.