The latest OOPhoto application can be experienced here.
The OOPhoto code for this post can be seen here.
Last week, I quickly coded the procedural version of OOPhoto, my latest attempt at learning object oriented programming in ColdFusion. After getting some good feedback from Peter Bell, I decided that the easiest next step would be for me to create some very simple service objects. This wouldn't entail much more than taking my SELECT-style queries and moving them into ColdFusion components. But, as easy as this would be, I decided that it would have some good benefits, summarized here:
Let's start out with the Dependency Injection as this is an important concept and one that makes complex object creation easy. Dependency Injection, or DI, sounds tricky, but in really it is just the moving of required objects into other objects. ColdFusion has a huge advantage in this area over other languages because in ColdFusion, object instantiation and object initialization are two separate steps. In fact, object initialization is not really something that is inherent to ColdFusion objects at all; sure, there is the pseudo-constructor, but things like the Init() method have emerged as an industry "best practice" and is in no way a required part of creating objects in ColdFusion.
Because object "preparation" uses these two different steps, it is quite easy to inject dependency objects after step one but before step two in the following manner:
Step 1: Create object using something like CreateObject( "component" ).
Step 2: Inject dependency objects.
Step 3: Initialize object, calling something like Init() on the object.
The simple fact that this behavior is available in ColdFusion makes creating circular references and other complex objects much less of a headache than it could be.
To enable our service objects to have their dependency objects injected into them during object creation, we have all the service objects extend a BaseService.cfc which has the utility method InjectDependency():
Launch code in new window » Download code as text file »
The InjectDependency() method does nothing more than take another object and store it in the VARIABLES scope of the target object. While this method could be dynamically added at runtime to all service objects, I decided to keep things as simple as I could for as long as I could. Having the method as part of the object that has package-only access seemed like the easiest realization of this utility.
Now, the InjectDependency() method is only a weapon in the battle; we still need someone to wield that weapon effectively. In our application, that warrior is the ObjectFactory.cfc. The Object Factory does nothing more than encapsulate the method by which other objects are created. So, for example, instead of calling something like:
Launch code in new window » Download code as text file »
... you would "ask" the object factory to create you an object of that type:
Launch code in new window » Download code as text file »
This prevents the calling page from having to know about any aspect of object creation other than the unique name of the object itself. This includes things like object paths, knowledge about Init() arguments, and, most importantly, any kind of dependency injection that needs to be done.
The OOPhoto object factory is created as a singleton (only one instance ever exists) and is cached in the APPLICATION scope:
Launch code in new window » Download code as text file »
Notice that in the Init() method of the object factory, I instantiate my three service objects. Then, I inject the dependency object (DSN). Then, as a final step, I call the Init() method on the object so that it can take care of any initialization that needs to be done internally. There are whole frameworks out there that take care of this kind of wiring for you, but for applications as simple as this, so far you can see there is no overhead to wiring this together yourself.
The service objects themselves are not that interesting, so I won't bother showing them all to you. I merely took the queries from the procedural pages and moved them into organized ColdFusion components. The real change that I made was to make the queries a bit more generic. So, for instance, on a page where I really only needed the ID and name of a record, I end up returning the entire record since I am not sure what the calling page will need.
If you are curious as to how they work, please take a look:
Now, rather than having the queries inline to the procedural page, I have code like this:
Launch code in new window » Download code as text file »
This was a small step in my application, but I think, a good step in the right direction. It was nice to see that Dependency Injection was much easier than I ever expected it to be. This just goes to show you that things you don't know or don't understand are often times much grander in your head than they are in reality. This is a good lesson to keep in mind as we continue on our journey to understanding object oriented programming.
So what's the next step? As I think Peter Bell's advice worked well for this last step, I might as well not switch horses in mid-stream; I'll go with Peter's next suggestion which was to create CFC-based controllers. Right now, my front-controller consists of several nested CFSwitch statements. CFSwitch statements are extremely easy to build and I have to say that I am a bit skeptical as to the benefits of a CFC-based controller; but, I will proceed regardless as come of Peter's arguments did make sense.
In my last post, Andy Matthews asked me if I would be honest with myself at the end of this process. To make sure that I stay honest, not only with myself, but with all of you, my dear readers, I have decided that at the end of each of these posts, I will start putting a brief "Reality Check" section with the following questions:
I think this step was definitely worth it. While some of the queries that I moved into the service objects were one-off style queries, several of them were repeated throughout the application. By moving them into service objects, I keep my application DRY (do-not repeat yourself) and I help to ensure that any changes to the queries need to be done in a minimal number of places.
Not even close. All we have done is refactored some code to remove duplication and implemented some patterns that make object creation easier. In reality, we have done nothing more than moved our procedural code into user defined functions (UDFs) that happen to be cached in object instances. All we have done is made our procedural code more organized; there are no aspects of object oriented programming present at this point.
Download Code Snippet ZIP File
Comments (6) | Post Comment | Ask Ben | Permalink | Other Searches | Print Page
Chase Bank - Worst And Seemingly Most Insecure Telephone Interface Ever!
OOPhoto - Moving To OOP - Adding Service Objects
Hmmm, I just realized that the upload-photo functionality is getting some weird error that says "Length Required". Not sure where I messed that up. I'll try to figure that out shortly.
Posted by Ben Nadel on Jul 21, 2008 at 9:49 AM
Fixed the "length required" issues. Some of my PhotoService.cfc methods required that a photo be joined to a gallery in an INNER JOIN fashion. Of course, not every photo can do that (photos uploaded for new galleries). So, no data was being returned. The code has been updated.
Posted by Ben Nadel on Jul 21, 2008 at 10:04 AM
I like the direction this is moving.
The reality check piece is a nice touch also and will provide some good points for conversation. Keep up the thorough reporting on your progress.
DW
Posted by Dan Wilson on Jul 21, 2008 at 11:57 AM
Exciting stuff. Looking forward to seeing how it progresses. As Dan says, I think the "reality check" is also a great idea.
Posted by Peter Bell on Jul 21, 2008 at 12:45 PM
@Ben, have you tried LightWire for the DI? I think rather than writing methods to handle that for you, you should take a look at it. I've been using it for quite some time and can't see building any app wtihout it. It leaves me to only think about what I need from my objects vs. thinking of what i need to pass to what to make it work. I've been reading your posts on the learning OO and it's one of those things that will help you along faster.
Posted by Hatem Jaber on Jul 22, 2008 at 7:36 PM
@Hatem,
I want to try to avoid all third-party frameworks until I feel that I have a good handle on all of this stuff. Until I know what problem they solve, I am not sure that I will be able to leverage them effectively.
But to answer your question, yes, I have seen LightWire. I've even been lucky enough to have Peter Bell (author) go over it with me personally.
Posted by Ben Nadel on Jul 23, 2008 at 8:23 AM