OO Principles: Encapsulating CFCs

I don't "do OO" development in ColdFusion. I'm starting with that statement not to spark another debate about whether to use OO in ColdFusion, but rather to clarify that while this post is about a principle of object oriented development, you don't need to "Do OO" in order to learn, use, and benefit from encapsulating CFCs.

When I first started using CFCs, I knew that encapsulation and decoupling were important, but this brought up new challenges. For example, if I had a method that queried a database, how would it know what datasource to use?

Before using CFCs, I would just use a request scoped variable to store the datasource. If, however, I access a request scoped datasource in a CFC then I am breaking encapsulation.

I could pass in the datasource with every method call, but that is a lot of extra code and it doesn't "feel" right. After all, if I am saving a record then the datasource isn't data for that record. Going down this path makes life more difficult and code less clear. But I don't want to break encapsulation either.

Fortunately, there is another way.

I should take a brief interlude to point out here that I am using CFCs as services, not objects. If you are using them as objects, then this is probably too basic for you. If you don't know the difference, don't worry about it just yet.

Instead of accessing the component by cfinvoke using the path of the component, I can instantiate it into a variable and use the instantiated component instead. The advantage here is that the instantiated component can store data within it (like a datasource, perhaps).

By convention, CFCs are instantiated using the "init" method (functions in a CFC are generally called methods).

So, I will write an init method that will take a datasource argument.

<cffunction name="init" access="public" returntype="any" output="no">
   <cfargument name="datasource" type="string" required="yes">
   
   <cfset variables.datasource = arguments.datasource>
   
   <cfreturn This>
</cffunction>

Most programmers put this method at the top of their component. Note that the datasource is copied from arguments scope (which lives only within the method itself) to variables scope (which is available to all methods in a component).

Assuming this is in a CFC named "Users.cfc", this is how I could load that into an Application - scoped component:

<cfset Application.Users = CreateObject("component","Users").init(datasource=request.dsn)>

The value of request.dsn is now stored in Application.Users. It is important to note here that the variables scope inside a CFC persists with the CFC instantiation itself. So, in this case, the variables scope in Users.cfc will exist for as long as the Application.Users variable does - even on subsequent page calls.

Now the Users component know about the datasource without breaking encapsulation and I don't have to pass it in to every method.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
This is also the way we do it (for now) I believe this is known as the Singleton pattern.
# Posted By Seth | 7/24/09 8:31 AM
Sure, if we wanted to sound all "OO Like". ;-)
# Posted By Steve Bryant | 7/24/09 10:47 AM
Nice post. I think it'll be great to see a series, showing how you've started to use some elements of attributes to solve specific problems.

For instance, now you have the datasource encapsulated within the Users component. But you may run into some other issues. Right now Users is in application scope. Nothing wrong with that, but lets say I have a Companies service that depends on the Users service. Right now it's probably calling application.Users. Again, not a problem, but imagine you want to write a unit test for Companies. It's now a pain to test Companies in isolation as it is calling the application scope directly.

What you *really* want is for Users to be injected into Companies. That way when you write a test you can just Companies.setUsers(MockUsers) to pass in a mock or stub implementation of the Users object so you can test Companies in isolation.

Of course, the common solution to this recuring problem us a DI engine such as ColdSpring or Lightwire. But I *do* think it is important to introduce CFC's to people one step at a time, showing the problems they solve and then the next set of problems that you run into (for certain classes of problems - for others you can get away with no objects or with a step on the path).

For me, the biggest benefit of CFC's is the capacity to create granularly testable code and I have a feeling that the many people who wonder about the benefits of CFC's would get it a little more if they tried TDD. But then of course they need to decide whether there are sufficient benefits in TDD!
# Posted By Peter Bell | 7/24/09 10:54 AM
Peter,

Thanks for the feedback. Indeed, the next posting I have planned is on composition. I started this one with the intent of writing about composition but I realized I needed to cover some more basics before I got to that.

I may eventually cover DI engines as well (I have my own that I created before Lightwire existed and before I had heard of ColdSpring which works a bit different than either of them).

I am a big fan of TDD as well. I just switched from CFUnit to MXUnit, both of which I really like.

In the meantime, I will look into making my SpamFilter a bit more forgiving. Sorry for the trouble on that.
# Posted By Steve Bryant | 7/24/09 11:13 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.