OOPhoto: VARIABLES vs. VARIABLES.Instance - When And Why?
The latest OOPhoto application can be experienced here.
The latest OOPhoto code can be seen here.
With OOPhoto, not only am I trying to learn object oriented programming in ColdFusion, I'm trying very hard to be able to explain every decision that I make. As far as I'm concerned, if I can't fully explain the choices that I make, then I probably shouldn't be making them. Today, it occurred to me that I have never explained, nor fully thought out the way in which I choose the storage of my internal component variables. Let's do that now.
If you look at my objects, you will see that I use two different component structures for data storage:
- VARIABLES
- VARIABLES.Instance
While one of these is contained within the other, they have two very different and distinct purposes. I can feel this difference very strongly, however, I am not so sure that I can gracefully put this difference into words.... but let's try.
What I think it comes down to is the concept of object-instance uniqueness. Let's say that I have to compare one object to another and that this comparison is the aggregate of the comparisons of all the "relevant" properties of each component. I say "relevant" here because that is the critical factor when it comes to storing our component properties. For deciding object-instance uniqueness, there are simply properties that are not relevant.
To explore this concept, let's look at a really small example, Dog.cfc. Imagine that Dog.cfc has these three properties (pseudo code notation):
Component:Dog
- DSN (Data source name)
- Breed
- Age
Now, imagine we have two instance of Dog.cfc. If we wanted to compare these instances to see if they were the same object, which values would we compare? Obviously, the Breed and Age are crucial - if one Dog has a different breed, then clearly these are different instances. As such, these two properties are very relevant to the object-instance uniqueness.
But, what about the DSN property? Theoretically, these two object-instances could have different DSN values; however, in the context of the "known" application - of our application - is this ever going to happen? No, it's not; all database interaction throughout my application will be done using the same DSN. Therefore, the DSN property of this class, Dog.cfc, is not relevant to object-instance uniqueness.
Ok, so now that we have a better understanding of what a "relevant" component property is, let's define the storage rules:
All properties that are relevant to object-instance uniqueness shall be stored in the VARIABLES.Instance struct.
All properties that are not relevant to object-instance uniqueness shall be stored in the VARIABLES scope.
Because the relativity of properties will change from context to context, we can't get any more specific than that. But, I believe that these rules are very clear in their intent and will allow me to explain my property storage decisions.
That being said, what happens when we have a Singleton object? A singleton is meant to have only one instance, so how can we apply a rule that involves the comparison of object-instances? That's easy - when you are dealing with a singleton, which can never be compared to like-instance, none of the properties of that singleton are relevant. As such, all properties of singletons should be stored in the VARIABLES scope.
Sometimes, it feels so good to write something out in words. Now, I never have to worry about property storage - that's decision is in the bag.
Reader Comments
seems like what you're really after is a static scope for the dsn, esp when multiple Bra.cfc's are instanciated in a request scope (or similar). well, maybe not for the DSN, but something to share across all instances perehaps.
sure there are work-arounds in CF if you juggle the architecture, but I've been glad I've had static variables in C# classes with some tricky situations, and CF hasn't quite got the same thing.
meh, my 2c
@Barry,
I am not sure that I am talking about a static class; although, I don't have much classical computer science training, so I might be very wrong.
These values don't have to be static. Take a Service Object, for instance. In my application, I have a CommentService.cfc class and a PhotoService.cfc class. The Comment service needs a reference to the Photo service instance. These are run-time classes, created by an object factory; however, there is only one comment service singleton and one photo service singleton. The photo service is injected into the comment service during creation....
I don't think either of these would be static? They are just not necessary for object uniqueness as they are both singletons.... hence, one gets stored in the other's VARIABLES scope.
Ok, so now that you have explained how you separate out the variables, can you tell me why?
If you as the writer of the component know the difference between the object's data, what benefit do you get from placing it in a different place? Is that in case you forget later, or do you have some dynamic code you use that programatically uses certain variables based on where they are stored?
why?
I do (or did) something similar to Ben. for me loose keys in VARIABLES scope are plumbing - stuff to keep the component itself going. the instance key is really internal data - important to that instanciation of that component instance.
it all comes down to having sufficent namespaces to park stuff.
BTW, talking about singletons can be a red herring. "there can be only one" is imposed externally from the component's POV. (there is a workaround where you can but) the component shouldn't know or care if it's a singleton.
Ben, those loose VARIABLES keys - the plumbing: that's what I'd like to see for a STATIC scope that is shared across all instances of that component. If they need to change per instance, then they're not plumbing and go into VARIABLES.instance.
meh, my 2c
That reason of yours is not very strong for using VARIABLES.instance, 'cause isn't DSN usually stored in the APPLICATION scope anyway?
However, I do use VARIABLES.instance in my project, just because Illudium PU-36 Code Generator generates the beans for me. At first I didn't know the reason why, but from what I remember, their argument was:
1.) to cfdump only variables you defined, because the VARIABLES scope has some built-in vars added by ColdFusion. I forgot what are those built-in vars, but you can try yourself.
2.) easier to compare/clone an object
However, for cfc that I wrote by hand, I usually don't have any reason strong enough to convince myself using an additional 'instance' scope. I guess I don't like the idea of another java map (hashmap?) look-up.
I'm in agreement with Henry and Barry (as they mentioned briefly), variables.instance is a great way to have a separate "namespace" for your internal class variables.
This can be handy for several reasons including;
- a dump() to easily see your entire class "state"
- separate your class variables vs things like dsn's and composed objects (as you mentioned already)
- obvious clarity in the difference between local method vars and class "instance" vars etc
http://www.chapter31.com/2007/06/14/using-variablesinstance-inside-your-components/
@Henry
meh, personal choice rules, OK?
I understand what Ben is doing and why and have similar thoughts. an extra hashmap lookup, not a bit deal in the scheme of things.
"cause isn't DSN usually stored in the APPLICATION scope anyway?"
IMHO, NEVER. or rather, unless there's special circumstances**, you will NEVER see the guts of any CFC of mine referring to an external scope. I use SERVER scope as much as APPLICATION and more likely or not will pass in an object into the init() for the DSN or a directory for CFFILE or whatever. But more often or not, ColdSpring is a good way to go, and Transfer for ORM work so it can turn into a bit of a moot point.
as I said, I can't see a problem with how Ben is doing it. At least not enough to change.
my 2c
** you gotta know the rules before you can break them
I think I am obligated to remind those who are learning CF to avoid the pitfall of just blindly follow conventions or practices in generals without understanding ultimately what they deliver, or how they'd help you, your team, or your app in the long run.
Challenge every bit of added complexity to CF that might slow down your CF app development. Evaluate the cost & benefits. :)
Another benefit to Railo is that we can scrap all this variables.instance crap because it is a performance hit. It's necessary with CF8, but it is costly.
@Justin - Not sure what you mean there, could you elaborate?
1. Performance hit for a simple 1 level struct? Would this even be a blip on the radar even under heavy load?
2. Part of the benefits of having this 2nd level "key" under variables includes encapsulating specifics of your object, things like properties vs plumbing vs methods etc
Does Railo have implicit "private" variables inside class (cfc) methods or something?
@Brad,
Excellent question - Why? I am sorry that I didn't answer that a bit better in my explanation.
At the end of the day, it is a little bit just about personal choice. However, I do like to separate the variables as described for a few reason:
1. I think it clearly defined the "intent" of the different variables. Instance-related variables (those specific to this instance only) go in the .Instance struct. And, general variables that might be shared by all related instances, go in the VARIABLES scope. These are two different classes of variable, and I like them to have their own home (it comforts me).
2. I do have some code that uses this in a generic way. If you look at the generic getter and setter methods, you will see that when a property is accessed or mutated, the generic method checks to see if the property exists in the VARIABLES.Instance struct. As such, it is convenient to have a struct that contains all the gettable and settable properties of the component.
3. And yes, as others have stated, this makes it easier to debug (CFDump) and compare components.
@Henry,
I think maybe I explained myself poorly - I did not mean to imply that the DSN was referenced directly in the APPLICATION scope. The way I would do this is actually pass the DSN struct into the target component either as a constructor argument or via dependency injection (DI is how OOPhoto works).
@Barry,
Again, I am not sure where people are getting this impression that I reference the APPLICATION scope from within my components. I did not state this anywhere. In fact, in my example, Bra.cfc has a DSN property (which it wouldn't need if it referenced the DSN in the APPLICATION scope).
@Henry,
re: questioning what people do - I couldn't agree more! In fact, as you can probably see, this entire OOPhoto series is all about questioning what I do and why I do it and making sure I can fully explain all my choices.
@Michael -
Railo allows you to configure your scopes as you would like so that the "this" scope in CFCs can actually be private rather than public.
Gert did a demo of speed differences between using variables.instance and "this" and it was pretty impressive. So, with Railo, you can have it behave the way ColdFusion behaves, or you can configure it to behave more strictly so that we can use the "this" scope like normal programmers.
@Justin,
Can you maybe explain a little bit as how you can use the Railo scenario to separate "plumping" variables from "instance" variables? I am not sure that I follow your parallels? (or perhaps you're just saying that Railo is faster in general?)
I don't know, you'd have to get Gert from team Railo on the horn. I'm sure it has something to do with the underlying Java and perhaps scope checking.
Surely this.address1 vs. variables.instance.address1 must be faster because it's shorter, right? :)
The main reason I use variables.instance.{variable} over variables.{variable} is because of name conflicts with function names and the private scope.
Far from a great example, but if I have a variables.email string set to an email address and a function called email, then I cannot access the email function via variables.email().
@Bradley,
Good point with the naming conflicts. I could see the same thing happening with an instance ID. For example, since ColdFusion components done *really* have unique id, sometimes people do this:
VARIABLES.ID = CreateUUID()
However, I am sure that most objects have a "property" ID as well:
VARIABLES.Instance.ID
The different containers allow for like-named elements with different intent.
@Ben, another good discussion.
I definitely like separating object variables in to different scopes.
A benefit I didn't see mentioned:
- transmitting values between objects/applications/systems.
By separating object variables in to different scopes, I was able to create functions like
struct = getDataVariables() - returns variables.data
and setDataVariables(struct) - set variables.data
(I use the data scope to contain the information an object was design to manipulate)
Example usage:
I have an object that represents a blog entry.
my entryObj contains the entry title, subjects, author, the entry, and other attributes.
I can use entryObj.getDataVariables() to get a struct containing that information.
I can then persist the struct as a JSON or WDDX string (title, author, categories etc),
When a user requests the page, instead of a complex database hit, I can
- create a new entry object
- load the JSON/WDDX string (from a file or db field)
- convert it back to a struct,
- use entryObj.setDataVariables(struct) to populate the new object
- pass the object to the appropriate page renderer (xhtml/flash/wml etc)
I can also send the JSON or WDDX string to a completely different application coded in a different programming language. As long as receiving application can decode JSON or WDDX, it can recreate the struct, and populate an equivalent object in that application.
I think this approach is called the memento pattern.
@Steve,
Nice example of how else this data separation can be leveraged.
Or should we as a CF community raise this issue to the CFML Language Advisory Committee, and ask for a better CFML standard?
How about adding another scope other than THIS and VARIABLES that works only with CFC? a new INSTANCE scope, anyone? :)
What do other languages do? Someone mentioned declaring the plumbing vars as Static. I can see that it makes sense most of the time, but does that make sense all the time?
@Henry,
I neither know enough about other programming languages, nor feel comfortable enough with object oriented programming to weigh in on this issue. I am just learning all of this for the first time :)
@Justin et al.
Struct access is so fast as to not be worth even thinking about.
Honestly, calling functions costs WAY more. I don't see anyone suggesting not breaking your code up into lots of readable, reusable, functions or objects because its "slow" ?
A lot of this "X is slow" stuff needs to be put to rest.
Using variables.instance or just plain variables has absolutely no measurable performance difference on anyone's code.
I needed to get up near 10000 accesses to get something measurable. variables.instance, variables.bar and instance.bar are all just as "slow". bar without a scope qualifier turns out to be the fastest, but I don't see anyone suggesting never scoping variables for performance.
The three previously mentioned methods are all about 4-6x as slow as unscoped accesses. Using an instance scope has no effect.
To put this in perspective, calling a function that does *nothing* is 11x slower than accessing an unscoped variable, and 2x slower than accessing a scoped one!
Calling createUUID() repeatedly is exponentially slower than accessing a struct member. In my test of 10000 iterations it took 6800x more time than the struct access. (there's probably a sleep in there or something, but the point is that if your objects use this function the instance scope is certainly totally unrelated to your performance hit).
Honestly, this is not the bottleneck in anyone's application. No one is doing 10k struct accesses per request, and even if they were, adding 7ms to a request is nothing when database access takes 50-200ms, and just plain outputting the page takes 20-100ms!
It's far more likely that your application is slow because you tried to be "Object Oriented" and created big arrays of objects instead of an iterator around a Query, or loaded large xml files and didn't cache the result, or any number of architectural things that have nothing to do with the core language.
Design your app right, don't worry about silly language things. Please? :P
(Railo might have different performance characteristics, but if struct access is Railo's bottleneck then something else is terribly wrong.)
(All testing done on CF8.0.1 on a C2D with 4GB of ram on JRun+Java 1.5)
I personally like to use a variables.instance structure. This also raises the question of where to declare your variables struct. If you have a pseudo constructor do you do this:
<cfcomponent>
<cffunction name="init">
<cfset variables.instance = StructNew() />
...
</cffunction>
</cfcomponent>
or
<cfcomponent>
<cfset variables.instance = StructNew() />
<cffunction name="init">
...
</cffunction>
</cfcomponent>
I always use the area after the opening <cfcomponent> tag, and sometimes even use cfparam just to make it clear what's happening.
When it comes to where to define these variables, I usually use the pseudo constructor (code inside of the CFComponent tag but outside of any CFFunctino tags). After a while, though, I find that I just repeat the same stuff in the pseudo constructor and in my explicit constructor (Init() method). As such, sometimes I do all of my variable setting in the explicit constructor (Init()).
I kind of go back and forth on this. Part me has this deep rooted desire to use the pseudo constructor, but not for any real reason. I like to be able to see all of the variables at the top. I think this rests partly on the fact that I don't trust people to use the Init() method or something - which is silly because it is generally required for a class to run correctly.
I think eventually, I will start to move all the variable setting into the Init() method. I might hurt the readability very slightly, but I think it will cut down on duplication.
I really had not thought about separation of static ( write once / read many ) variables from the dynamic variables in components, but it is an interesting idea.
I dislike using the variables scope directly, but setting up a variables.static structure is very appealing.
You could setup onMissingMethod set/get logic to allow multiple read/write to keys in variables.instance and set once/read many logic for keys in variables.static.
@Henry Ho,
"How about adding another scope other than THIS and VARIABLES that works only with CFC? a new INSTANCE scope, anyone? :)"
In a way I agree but in a way I just shrug my shoulders and say "meh". After all, one has to wonder exactly what was going on that they yet again had to have CF blaze new but misguided trails in having THIS be available publicly.
@Allen,
I have not programmed in many languages, but, isn't the THIS scope generally a public scope? In ColdFusion it is, in Javascript it is (which came out before ColdFusion did). I think the THIS scope is also public in Java, but I cannot remember.
Hum.. interestingly I just asked this question last week because I forgot the reason behind Variables.instance scope.
CFCDev: Can someone remind me, why variables.instance?
http://groups.google.com/group/cfcdev/browse_thread/thread/9f52a824eec12fc1#
As for using This scope, I've found many CFer's actually do not use or never heard of using getter and setters... so they just use This scope... oh well, whatever works, right? :)
@Henry,
Good conversation over on CFCDev. I think people have good points there about why having VARIABLES.Instance has some value.