Creating An Abstract Base Class In ColdFusion
An Abstract Base Class in object oriented programming is a class that is meant to be extended by sub-classes but is never meant to be instantiated directly. The base class is designed to provide a common interface and set of core functionality to all of its sub-classes. Now, while an abstract base class is not meant to be instantiated directly, often times, it's constructor functionality is part of the core functionality that we want to make available to its sub-classes. So the question is: How do we give the base class a core constructor but not let that constructor be called externally?
The answer is actually quite simple:
- Make the base constructor private.
- Have the base constructor return VOID.
Point 1 above is really the key; point 2 above is not really required, but it makes a strong statement about how the constructor is to be used by its calling context (as such, it is something that I like to do out of habit). Now, because ColdFusion Init()-style constructors are really just a convention and we don't want the base class to be instantiated directly, it is important that our base Init() method must perform mission critical setup; otherwise, a user could simply instantiate the base class and just not call the Init() method.
Let's look a small code example. We are going to create a base class, Person.cfc, and a sub-class, GenderedPerson.cfc (it's a weak example, but just go with it). So first, let's examine our abstract base class:
<cfcomponent
output="false"
hint="I provide the abstract base functionality for Person.">
<!---
To make sure that the outside world cannot instantiate
and then initialize this component, set the access rights
of this method to Private. That way, it can ONLY be
accessed by internal methods and the extending classes.
--->
<cffunction
name="Init"
access="private"
returntype="void"
output="false"
hint="I provide super-constructor functionality for the Person sub-classes.">
<!--- Define arguments. --->
<cfargument
name="Name"
type="string"
required="true"
hint="I am the name of this person."
/>
<!--- Set up instance variables. --->
<cfset VARIABLES.Instance = {
Name = ARGUMENTS.Name
} />
<!---
Return out. Do NOT return THIS scope (as we would
normally) as we want to try and prevent this
constructor from being used as our only constructor.
--->
<cfreturn />
</cffunction>
<cffunction
name="GetName"
access="public"
returntype="string"
output="false"
hint="I return the person name.">
<cfreturn VARIABLES.Instance.Name />
</cffunction>
</cfcomponent>
As you can see, our base class constructor - Init() - has its access set to Private. This will prevent any external context from calling it. Then, just to make sure (really more for proper habit), I am returning VOID from the Init() method just to drive home the point that this method is not meant to return an initialized object. Now, despite the fact that it doesn't return any object, it does initialize the instance variables structure (which will be used by both our base class and our sub-classes).
Now, let's create our sub-class, GenderedPerson.cfc:
<cfcomponent
extends="Person"
output="false"
hint="I am a gendered Person.">
<cffunction
name="Init"
access="public"
returntype="any"
output="false"
hint="I return an initialized component.">
<!--- Define arguments. --->
<cfargument
name="Name"
type="string"
required="true"
hint="I am the name of this person."
/>
<cfargument
name="Gender"
type="string"
required="true"
hint="I am the gender of this person."
/>
<!---
Call SUPER constructor and pass in the required
argument. The super contsructor will take care
of creating our instance variable structures;
afterwards, we simply will need to add to it (not
create it).
--->
<cfset SUPER.Init( ARGUMENTS.Name ) />
<!--- Set up instance variables. --->
<cfset VARIABLES.Instance.Gender = ARGUMENTS.Gender />
<!--- Return This reference. --->
<cfreturn THIS />
</cffunction>
<cffunction
name="GetGender"
access="public"
returntype="string"
output="false"
hint="I return the person gender.">
<cfreturn VARIABLES.Instance.Gender />
</cffunction>
</cfcomponent>
As with any sub-class, you will see that it extends our base class as part of the CFComponent tag. Because this sub-class is meant to be instantiated, its Init() method is publicly accessible. But, just because it has its own Init() method, that does not mean we want to ignore our base Init() method. In fact, we are going to need it to create a properly initialized object. As such, inside the sub-class Init() method, you will see that it calls the Init() method of the base class:
<cfset SUPER.Init( ARGUMENTS.Name ) />
Now, even though the Init() method of the base class is private, because the sub-class is basically the same object (at instantiation time), it has access to the base class' private methods. It uses the base Init() method to set up the core instance variables and then it adds its own.
To make sure this is all running properly, let's do a quick test:
<!--- Create an instance of the person. --->
<cfset objPerson = CreateObject( "component", "Person" ).Init(
Name = "Ben Nadel"
) />
Here, we are trying to create an instance of the base class. And, as expected, ColdFusion throws the following error:
The method Init was not found in component ....Person.cfc. Ensure that the method is defined, and that it is spelled correctly.
Good. Now, let's try to create an instance of the sub-class:
<!--- Create an instance of the gendered person. --->
<cfset objBoy = CreateObject( "component", "GenderedPerson" ).Init(
Name = "Ben Nadel",
Gender = "Male"
) />
<cfoutput>
#objBoy.GetName()#<br />
#objBoy.GetGender()#
</cfoutput>
This runs perfectly and outputs the following:
Ben Nadel
Male
As you can see, our sub-class is able to leverage all of the core functionality provided by the Abstract Base Class: the Init() method as well as the core instance variable, Name, and its Getter method.
The Abstract Base Class is definitely a cool thing; but, it does force us to look at an even bigger picture question - What is the purpose of the Init() method? When I first started building ColdFusion components back in the CFMX 6.1 days, I used to put all of my initialization code in the pseudo-constructor (the area inside of the CFComponent tag but between the CFFunction tags). Then, when I started to learn the Init() convention, I was a bit stuck - I was no longer sure where to put initialization code. For a while, I was actually duplicating it in a way - I would set default instance values in the pseudo-constructor and then I would store the "real" values in the Init() method.
This methodology created a lot of copy-paste coding with zero benefit; but, for some reason, I couldn't figure out how to make it better. Lately, as I have gotten more into Object Oriented Programming (OOP), I have been able to make up my mind - all Initialization code goes in the Init() method. When I finally stopped thinking of the Init() method as a mere convention and started to think about what the Init() method actually did, it was instantly clear - the Init() method initializes an object. And, if I ever create an object and fail to call Init(), I have failed to uphold the "initialization contract", and it is to be rightly expected that the resultant CFC will fail to work properly.
In essence, I have gone from a boy fearing that someone might create a CFC without calling Init() to a man that simply demands that Init() methods be called. And, as you can see with the Abstract Base Class, this demand lays the groundwork for some sweet functionality that would not be available otherwise.
NOTE: There are always exceptions to the rule. Most notable is the use of public API components in which ColdFusion components cannot be properly instantiated.
Want to use code from this post? Check out the license.
Reader Comments
Interesting approach. I do it a bit differently. The one thing I don't like about the approach that you are describing is that you end up repeating the arguments for the base init() method in each of the sub-classes' init methods. To me that feels like I'm repeating myself.
Instead I create an additional method, which I call configure(), which is called by the init() method of the base class. I then omit the init() method from the sub-classes, and use the configure() method to do any sub-class specific setup.
This allows me to not have to duplicate any of the code of the base class into the sub-class, and also prevents me from accidentally messing up the signature of the init method.
Of course this doesn't address the whole idea of actually preventing the base class from being instantiated, which your approach does, but I personally don't feel that that is necessary. Perhaps being part of a team of one helps me feel that way ;-)
@Bob,
I just wanted to address the necessity of this technique first. Yes, this is not necessary, but I think it is nice as it doesn't require *extra* to make it work (as with something like CFInterface). But, with a mere shifting of my own personal "best practices", I think it makes a nice statement about the code.
To me, this is just a new convention that I am using. But, like I said in the blog, when you use this new convention, it forces you to examine what an Init() method really is, which forces you to decide where your initialization code might go.
Can you explain to me the Configure() method? I am not sure how this prevents you from duplicating? I don't see the sub-classing as really being a duplication of the arguments as each sub-classed object might have a completely different Init() method signature.
It's true that it's possible that each sub-classed object might have a completely different Init() method signature, but in my mind that's far less likely than there being some duplication required. Let's look at your example in the post for the duplication that I'm talking about.
In the base init() method you expect an argument for "Name" because all Persons will have a Name. In the subclass init() method you also have to specify that it expects an argument for Name, so that you can turn around and pass it through to super.init(). Personally I find that to be a common scenario: the subclasses have common properties which they inherit from the superclass. If your superclass had 10 of these common properties, and you had 5 subclasses, then you'd have to duplicate those arguments 50 times (10 arguments x 5 subclasses). That's what rubs me the wrong way.
I'd rather have the superclass take care of all of that itself, without the subclass having to worry about all of the properties of its superclass. And that's what the Configure() method allows me to do.
There is one additional downside to the configure() method: I don't end up with a clear API for my subclasses. I have to look at both the init() method in the superclass and the configure() method in the subclass to know what arguments need to be passed in, but that's another thing I'm willing to live with. In order to have abstract code I often compromise on a clear API. Again, something I can probably afford to do being a sole developer.
Does that answer your question?
Timely post for me since I'm in the middle of re-factoring a lot of code using the principles outlined in Bob's posts on "How I Use Transfer" describing AbstractService, AbstractGateways, etc. I'm posting so I can both follow this discussion and to pose the additional question of how implementing ColdSpring's inherent init() calls and the use of parent classes plays a role in all this?
@Bob,
I'm not clear exactly on how your configure() method actually cuts down on duplication? At some point, arguments need to be defined and then set. Can you be more explicit on how that is happening? If you like, take my example and explain what the configure() method would do.
@Bim,
Unfortunately, I cannot speak to the ColdSpring issues as I have not used it. Perhaps someone else here can.
@Bim: ColdSpring won't know anything about the base class. If you're defining beans, those are the concrete implementations. So ColdSpring just calls the concrete class's constructor. Anything beyond that is up to you (like having the concrete constructor do a super.init()).
Sorry Ben, I guess I wasn't clear about how it works. Taking your example, and integrating it with the configure() idea would yield something like this (slimmed down):
Base class:
<code>
<cffunction name="Init" access="public" returntype="any">
<cfargument name="Name" type="string" required="true" />
<cfset VARIABLES.Instance = { Name = ARGUMENTS.Name } />
<cfset configure(argumentCollection=arguments) />
<cfreturn this />
</cffunction>
<cffunction name="configure" access="public" returntype="void">
</cffunction>
</code>
Sub class:
No init() method is defined in the sub-class.
<code>
<cffunction name="configure" access="public" returntype="void">
<cfargument name="Gender" type="string" required="true" />
<cfset VARIABLES.Instance.Gender = ARGUMENTS.Gender />
</cffunction>
</code>
I hope that the above code renders properly on your blog ;-)
So, in this example I'm only coding for the Name argument in the base class. There is no reference to it in the sub-class. Whereas in your example you are coding for it in both places. Which sure looks like duplication to me.
Again, I realize that there are drawbacks to my approach, so I'm not trying to say that it's better, or that your duplication is something that must be avoided at all costs. I'm just pointing out that there is a different way to do it which could avoid some of that duplication.
Have I been more successful in explaining myself this time?
@Bob,
OK, that makes sense. The configure() method is defined in the sub-class, not the base class. Interesting. So in order to see the "Init" of a sub-class, you just need to look into the Configure() method rather than the Init() method (which doesn't exist).
(sorry code doesn't render better in my comments). I should make that my next priority.
That's one of the issues - in order to see the "Init" of a sub-class, you have to look at both the configure() method of the subclass and the init() method of the superclass. Which some people may not like. I'm prepared to live with it in order to reap the benefits of everything only being defined in a single place.
Since the init function initializes an object, is it wrong to require one to call a configure method before the object will actually work properly? I'm asking because instead of using something like ColdSpring, I simply instantiate all my components first, then call configure methods to set dependencies. I can't simply pass everything the class needs into the init function because of cases of circular dependence. Aside from just feeling a bit off, can anybody foresee any serious issues with the setup?
Honestly, I've only ever used this approach to do things that don't require arguments. The example I posted earlier, using the argumentCollection was an invention of mine to make it fit Ben's use case.
Thinking about it more, it seems like one could possibly do a combination of the two approaches, forgetting about configure() and just using init() in both objects, and using argumentCollection to allow for passing arguments without having to code for them in each object.
This is all ottomh, so maybe it's a load of hooey, but it makes a bit of sense to me.
@Bob,
I am not so concerned about that (having to look at the configure() method). I would say that people probably don't have to look much at the Init() method as that is the "common" stuff. And, as you don't often use this with arguments, I can see that this does have a nice payoff.
@PJ,
Circular references are an issue unto themselves. I don't know how to best handle them. I think you have two options:
1. Not make them a required part of initialization and then set up the bi-directional linking aferwards.
2. Create one both objects but DO NOT INITIALIZE them (do not call Init()). Then, call each other's Init() methods, passing in the non-initialized CFC reference. This act will allow you to pass in both objects and initialize them at the same time (more or less).
I wish I knew what you guys were on about. Some day...some day...!
@Michael,
I think we're all still figuring it out also :)
Hi All.
I have been coding in .NET C# recently, and a lot of the time you will have your class variables defined before you even declare any methods, for example:
class Person
{
private String name;
private int age;
public Person(String tName, int tAge)
{
name = tName;
age = tAge;
}
}
Simple enough. Obviouls yyou will also have the getters and setters etc.
But my question is this - in Coldfusion, can you do the same, for example:
<cfcomponent
output="false"
hint="I provide the abstract base functionality for Person.">
<cfset variables.instance.name = "" />
<cfset variables.instance.age = 0 />
<cffunction name="Init" returnType="void">
<cfargument name="tName" type="string" required="true" />
<cfargument name="tAge" type="numeric" required="true" />
<cfset variables.instance.name = arguments.tName />
<cfset variables.instance.age = arguments.tAge />
<cfreturn />
</cffunction>
</cfcomponent>
What are the implications doing it this way?
How do you ensure that the variables are private, like in the .NET example.
Also, why do all the CF crew use variables.instance.varName instead of just instance.varName?? It's a hell of a lot more to write out and at the end of the day, when you want to dump values to debug, you can just dump instance for the class info, or variables for the variable info?
Some of these questions may be novice - but I'm coming from a very procedurally-oriented development background and am also trying to embrace OO in CF :)
Thanks
@Paolo,
Yes, you can set variables in what is known as the pseudo-constructor, the area inside of the CFComponent tags, outside of the CFFunction tags (as you do in your .NET classes).
The reason that people use VARIABLES.Instance rather than just Instance is that the VARIABLES scope is the "private" scope of the ColdFusion component. Had they stored it in just the Instance scope, it would be available to the world without the use of any getter methods.
Another quickie...
If you set variables in the pseudo constructor - is it possible to define them without any values, like in .NET, for example, can you do this:
<cfset variables.instance.name />
<cfset variables.instance.age />
instead of:
<cfset variables.instance.name = "" />
<cfset variables.instance.age = 0 />
Would that work or crash?
I suppose I could just test that myself :)
Cheers
@Paolo,
No, that crashes :) You have to give them a default value.