Many times object-oriented programming (OOP) is billed as the end-all solution to cure the spaghetti code that can come from procedural style applications. After all, you just have to stuff your logic code into a component (big OOP buzzword - encapsulation), and now your code is instantly better, right? How hard is it to stick a createObject function call or a <cfobject> tag in when you need to access that bit of code? Can anybody look into the future and see a problem here?
Let's look at an example. Many object-oriented articles use cars as examples, so we'll stick with the trend. When we need to find out information about a given car, we simply instantiate the Car object as follows:
<cfset oCar = createObject('component', 'com.mydomain.Car').init() />
Nice and easy, right? However, our application is going to get a bit more complex. Our car is not going to do much without an engine and a transmission. An engine and a transmission are not a simple property though; these are also objects with their own properties. In order to build our model, we need a couple more lines of code.
<cfset oEngine = createObject('component', 'com.mydomain.Engine').init() />
<cfset oTrasmission = createObject('component', 'com.mydomain.Transmission').init() />
<cfset oCar = createObject('component', 'com.mydomain.Car').init(oEngine, oTrasmission) />
Still, not too bad. But what happens when the business model continues to grow? None of us have ever experienced anything called scope creep, right?
The Problem
Every time we need a new Car instance, we have to remember all of the different dependencies a Car object has (the engine, the transmission, etc.) and write all of the different createObject function calls (in the right order) while getting the component path for each one correct. But wait, at the moment, our Car object is kind of dumb. We need to go to the database and load information about our car, right? We need to create some data access objects, each of which requires a data source object. The stack of objects we need just to create a car almost makes you think about going back to procedural programming!
Add in the difficulty of a component path changing or a new dependency being added to the car object and all of a sudden we have spaghetti code all over again. So much for OOP being the solution to all the world's (or at least software developments) problems!
Introducing Design Patterns
If OOP is not the solution, then Design Patterns must be, right? Well, maybe. Design Patterns are simply standard solutions to common issues that arise in software design. Many design patterns apply to object-oriented development, but not all of them. A design pattern is not a piece of code that you can simply plug into your application. It is more of a template that offers guidance on how to solve different problems that may arise.
Some different examples of design patterns include:
How does this apply to object-oriented development?
The Object Factory
Let's take the auto assembly plant and turn it into an object model. In the business case, I know that a Car is made up of a whole series of other objects - things like an engine, transmission, seats, and even the stereo system. I don't want to keep track of all of these dependencies every time I need a new car. The solution? An auto assembly plant or, in other words, an object factory. I need an object that I can ask to give me instances of other objects. In this example, I would have a Car factory object that I could ask for different types of cars and the factory object would then give me the completed object without my code having to know anything about what makes up a Car.
Since we are developers, we like to see code. What would a factory object look like?
<cfcomponent>
<cffunction name="getCar" access="public" returntype="com.myDomain.Car">
<cfargument name="make" type="string" required="true">
<cfswitch expression="#arguments.make#">
<cfcase value="corvette">
<!--- create corvette object dependent objects --->
<!--- create and return corvette car object --->
</cfcase>
<cfcase value="xlr">
<!--- create cadillac xlr object dependent objects --->
<!--- create and return cadillac xlr car object --->
</cfcase>
</cfswitch>
</cffunction>
</cfcomponent>
Now, anytime I need to create a new Corvette, I just ask my factory for one like this:
<cfset oCorvette = oCarFactory.getCar('corvette') />
This is much, much better. Now, we have one place for all instantiation code instead of having it spread throughout the application. If we ever need to change the path of a component or add a new dependency, we just have to make the change in the factory. However...
You had to say it, didn't you? If you look at the code for our car factory and look into the future a bit, you will see that really, what we have done is move the spaghetti code from our application into the factory object. The code within that factory object is going to get quite complex, as for each type of car we create, we have to know all of the dependencies that that type of car has. Some of these dependencies will more than likely be shared by multiple types of cars as well, which means if the instantiation of an engine changes, we'll have to find all of the instances of that code in our factory and update them. While this is better than nothing, there is still room for improvement.
The Smart Object Factory
Along the way, some very
smart people have taken the concept of the object factory and expanded
upon it. Here enters the term Dependency Injection. What if, instead of
building a huge switch statement inside of my factory, my factory was
smart enough to handle all of the dependencies itself? For example,
what if I could define for the factory a blueprint that says when you
build an instance of a Car object, it is dependent upon instances of an
Engine object and a Transmission object. At this point, my only concern
is the Car object, not how to build the Engine or Transmission object.
Later on in the plans, I define that when the factory builds an
instance of an Engine, it is dependent upon an instance of a Cylinder
object.
What have I done here? By separating the logic of building the Engine object away from the logic of building the Car object, I have encapsulated that logic. Now, if a Truck object also depends upon the Engine object, it can use the same set of blueprints that the Car object uses. This means if I have to change the way the Engine object is instantiated, I can do it in one place and let the factory handle putting all of the pieces together.
This sounds a whole lot more complicated than our little factory component. Never fear, somebody else has already done all of the heavy lifting for us. A framework has been developed called ColdSpring by Chris Scott and Dave Ross. The purpose of the ColdSpring framework is to "make the configuration and dependencies of CFCs easier to manage." Sounds exactly like what we are looking for. Let's look at some of the high-level help that ColdSpring can provide our application:
<bean id="Car" class="com.myDomain.Car">
<constructor-arg name="Engine">
<ref bean="Engine" />
</constructor-arg>
<constructor-arg name="Transmission">
<ref bean="Transmission" />
</constructor-arg>
</bean>
<bean id="Engine" class="com.myDomain.Engine" />
<bean id="Transmission" class="com.myDomain.Transmission" />
As you can see, we have specified a bean (object) definition for our Car object. The class (object path) is provided as well as a series of constructor arguments. This means, when ColdSpring creates a new instance of the Car object, it must also pass all of the listed arguments into that Car object's constructor method. In this case, the arguments include an instance of the Engine bean and the Transmission bean. Later on in the file, we find the bean definitions for the Engine and Transmission beans.
By utilizing ColdSpring and its XML blueprint, we have significantly cut the amount of code necessary to create new objects. Even within our factory, we no longer have to know all of the details of the dependent objects when we are defining a given object.
Conclusion
While object-oriented programming
offers a wide array of benefits, it is still possible to write bad OO
code just as you can write bad procedural code. An object factory is
one of many good practices that, when added to your bag of tricks, can
help cure the object explosion that tends to happen in many OO
applications.
If you would like to find out more about some of the topics we only had room to briefly touch on here, check out the following resources.
• Freeman, E.; Freeman, E.; Sierra, K.; and Bates, B. (2004). Design Patterns - Head First Design Patterns. O'Reilly: www.oreilly.com/catalog/hfdesignpat/
• Corfield, S. "Managing ColdFusion Components with Factories": http://corfield.org/articles/cfobj_factories.pdf
• ColdSpring Framework (Chris Scott and Dave Ross) www.coldspringframework.org/