Implement the State pattern with ColdFusion Components
Takeaway: The introduction of ColdFusion Components (CFCs) in ColdFusion MX opened the door to more object-oriented programming in CF. When application developers start thinking about OOP, the idea of design patterns almost always comes up. In that context, Brian Kotek discusses the State pattern: its purpose, the problems it can solve, and how you can implement it using CFCs.
This article is also available as a TechRepublic download, which includes all the sample code in a more manageable text format.
The introduction of ColdFusion Components (CFCs) in ColdFusion MX opened the door to more object-oriented programming in CF. When application developers and programmers start thinking about OOP, the idea of design patterns almost always comes up. In that context, let's talk about the State pattern: its purpose, the problems it can solve, and how you can implement it using CFCs. Several code listings are included here, but you can also get all the code in the download version of this article.
A first crack at the problem
To begin with, I'd like to look at a problem that might lead us to consider the State pattern as a solution. In my example, I'm creating a simple content management system. The content items in my system can have several statuses: draft, in review, awaiting publication approval, and published. A content item can also have various actions performed on it: save the item, approve the item, or reject the item. Depending on the status and what action is being performed on the content item, it needs to react in a different way.
There are a few ways one might try to approach the problem. The first might be to create different methods for each combination of status and action. The Content Item CFC is shown in Listing A, and a test run of using that CFC is shown in Listing B.
Listing A
<cfcomponent name="MethodExplosionContentItem" hint="I am a per-reqeust CFC that represents a Content Item, but does not use the State pattern.">
<cffunction name="init" access="public" returntype="MethodExplosionContentItem" hint="Constructor.">
<cfreturn this />
</cffunction>
<cffunction name="saveDraft" access="public" returntype="void" output="true" hint="">
<cfoutput>
Draft: saving content item...<br/>
</cfoutput>
</cffunction>
<cffunction name="savePublished" access="public" returntype="void" output="true" hint="">
<cfoutput>
Published: setting the content to draft mode...<br/>
</cfoutput>
</cffunction>
<cffunction name="approveDraft" access="public" returntype="void" output="true" hint="">
<cfoutput>
Draft: saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
</cffunction>
<cffunction name="approveReview" access="public" returntype="void" output="true" hint="">
<cfoutput>
Review: alerting content author that content is approved...<br/>
Review: marking content as ready for publish approval...<br/>
</cfoutput>
</cffunction>
<cffunction name="approvePublishApproval" access="public" returntype="void" output="true" hint="">
<cfoutput>
Publish approval: Marking content as deployed...<br/>
Publish approval: Pushing live and updating content cache...<br/>
</cfoutput>
</cffunction>
<cffunction name="rejectReview" access="public" returntype="void" output="true" hint="">
<cfoutput>
Review: alerting content author that content is rejected...<br/>
Review: setting back to draft mode...<br/>
</cfoutput>
</cffunction>
<cffunction name="rejectPublishApproval" access="public" returntype="void" output="true" hint="">
<cfoutput>
Publish approval: alerting reviewer that publishing is rejected...<br/>
Publish approval: setting back to review mode...<br/>
</cfoutput>
</cffunction>
</cfcomponent>
Listing B
<h2>First, method explosion with separate methods for all actions and statues:</h2>
<cfset contentMethodExplosion = createObject('component','MethodExplosionContentItem').init() />
<cfset contentMethodExplosion.saveDraft() />
<cfset contentMethodExplosion.approveDraft() />
<cfset contentMethodExplosion.approveReview() />
<cfset contentMethodExplosion.approvePublishApproval() />
<cfset contentMethodExplosion.savePublished() />
<cfset contentMethodExplosion.approveDraft() />
<cfset contentMethodExplosion.rejectReview() />
<hr/>
<cfset contentMethodExplosion2 = createObject('component','MethodExplosionContentItem').init() />
<cfset contentMethodExplosion2.rejectReview() />
<cfset contentMethodExplosion2.saveDraft() />
<cfset contentMethodExplosion2.approveDraft() />
<cfset contentMethodExplosion2.approveReview() />
<cfset contentMethodExplosion2.rejectPublishApproval() />
<hr/>
I have to say up front that this code works. It does exactly what we needed it to do. However, there are some problems with how it's doing its job. First, there are a lot of methods; one for each combination of status and action. We have saveDraft(), savePublished(), rejectReview(), rejectPublishApproval(), etc. If this raises a red flag in your mind, good; because it should. If we need to add any more statuses, or any more actions, the possible combinations of status and event will keep growing exponentially. Three statuses and three actions equal nine methods. Four statuses and four actions equal 16 methods, and so on. Clearly, this isn't a very scalable solution.
That's not all. There's another problem. We're forcing the client code (in this case, my simple test template) to keep track of what status the content item is in. If I lose track of the status and accidentally call approvePublishApproval() when I meant to call approveDraft(), I've just put the content item into a bad state. Maybe there will be an error. Or maybe the content item just got published when it shouldn't have. The point is, the client code shouldn't have to be keeping track of this. The Content Item should be responsible for keeping track of its own status.
A second take
Okay, so we all acknowledge that the "method explosion" approach is probably not the best solution. Maybe we can simplify things.
Ideally, I think the Content Item should just respond to a more generic action request. This would bring the number of methods the Content Item needs back down to three: save(), approve(), and reject(). I have an example of testing this in Listing C.
Listing C
<h2>Next, using conditional logic in Content Item:</h2>
<cfset contentConditional = createObject('component','ConditionalContentItem').init('draft') />
Content item created in <strong>draft</strong>...<br/>
<cfset contentConditional.save() />
<cfset contentConditional.approve() />
<cfset contentConditional.approve() />
<cfset contentConditional.approve() />
<cfset contentConditional.save() />
<cfset contentConditional.approve() />
<cfset contentConditional.reject() />
<hr/>
<cfset contentConditional2 = createObject('component','ConditionalContentItem').init('review') />
Content item created in <strong>review</strong>...<br/>
<cfset contentConditional2.reject() />
<cfset contentConditional2.save() />
<cfset contentConditional2.approve() />
<cfset contentConditional2.approve() />
<cfset contentConditional2.reject() />
<hr/>
Well, that certainly looks simpler. And notice that the client code now doesn't know what status should be called next. It just calls approve(), and the Content Item keeps track internally of what that means and how it should respond. Problem solved!
Okay, not so fast. Yes, we've simplified the public interface to our Content Item, and we've moved the responsibility of keeping track of the status into the Content Item where it belongs. But what is the cost we pay for these benefits?
Listing D shows the code for this version of the CFC. I'll warn you, it's pretty nasty. Look at all that conditional logic. For each method call, I need conditional checks for all the different statuses to figure out what to do. And again, adding more statuses or actions still means an explosion, just a different kind of explosion. In this case, an explosion of conditional statements is the result.
Listing D
<cfcomponent name="ConditionalContentItem" hint="I am a per-reqeust CFC that represents a Content Item, but does not use the State pattern.">
<cffunction name="init" access="public" returntype="ConditionalContentItem" hint="Constructor.">
<cfargument name="initialStatus" type="string" required="true" />
<cfset setStatus(arguments.initialStatus) />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'draft'>
<cfoutput>
Status '#getStatus()#' saving content item...<br/>
</cfoutput>
<cfelseif getStatus() eq 'published'>
<cfoutput>
Status '#getStatus()#' setting the content to draft mode...<br/>
</cfoutput>
<cfset setStatus('draft') />
</cfif>
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'draft'>
<cfoutput>
Status '#getStatus()#' saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
<cfset setStatus('review') />
<cfelseif getStatus() eq 'review'>
<cfoutput>
Status '#getStatus()#' alerting content author that content is approved...<br/>
Status '#getStatus()#' marking content as ready for publish approval...<br/>
</cfoutput>
<cfset setStatus('publishApproval') />
<cfelseif getStatus() eq 'publishApproval'>
<cfoutput>
Status '#getStatus()#' Marking content as deployed...<br/>
Status '#getStatus()#' Pushing live and updating content cache...<br/>
</cfoutput>
<cfset setStatus('published') />
</cfif>
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'review'>
<cfoutput>
Status '#getStatus()#' alerting content author that content is rejected...<br/>
Status '#getStatus()#' setting back to draft mode...<br/>
</cfoutput>
<cfset setStatus('draft') />
<cfelseif getStatus() eq 'publishApproval'>
<cfoutput>
Status '#getStatus()#' alerting reviewer that publishing is rejected...<br/>
Status '#getStatus()#' setting back to review mode...<br/>
</cfoutput>
</cfif>
</cffunction>
<cffunction name="getStatus" access="private" returntype="string" output="false" roles="" hint="I return the status.">
<cfreturn variables.instance.status />
</cffunction>
<cffunction name="setStatus" access="private" returntype="void" output="false" roles="" hint="I set the status.">
<cfargument name="status" type="string" required="true" hint="status" />
<cfset variables.instance.status = arguments.status />
</cffunction>
</cfcomponent>
This might be better than the first solution, but I still think there are too many negatives. And this is where we turn to the State pattern to see if it can offer any help.
The State pattern defined
The State pattern was introduced to the world in the seminal book Design Patterns, by Gamma, Helms, Johnson, and Vlissides (the "Gang of Four"). In that book, they list the intent of the State pattern as "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class."
Even without knowing anything else about the State pattern, just that description might already be making you raise an eyebrow. "Alter its behavior when its internal state changes" sounds an awful lot like what we want our Content Item to do.
Applying the State pattern to the problem
Essentially, what the State pattern does is let us encapsulate the different states (the Content Item statuses in this example) along with the behavior needed for each state into their own CFCs. Each state knows how to respond to the different actions and knows which state to set the Content Item to in a given context. We keep our simple interface to the Content Item (to be clear I've called it ContentItemContext in this version of the solution), but we avoid the conditional explosion of the previous example. You can see the ContentItemContext in Listing E, the State CFC code in Listing F, and the test to run it in Listing G.
Listing E
<cfcomponent name="ContentItemContext" hint="I am a per-reqeust CFC that represents a Content Item. All instance data about the item would go here.">
<cffunction name="init" access="public" returntype="ContentItemContext" hint="Constructor.">
<cfargument name="draftState" type="DraftState" required="true" />
<cfargument name="reviewState" type="ReviewState" required="true" />
<cfargument name="approvePublishState" type="ApprovePublishState" required="true" />
<cfargument name="publishedState" type="PublishedState" required="true" />
<cfargument name="initialState" type="string" required="true" />
<cfset variables.instance.draftState = arguments.draftState />
<cfset variables.instance.reviewState = arguments.reviewState />
<cfset variables.instance.approvePublishState = arguments.approvePublishState />
<cfset variables.instance.publishedState = arguments.publishedState />
<cfset createStateNameMappings() />
<cfset setStateByName(arguments.initialState) />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().save(this) />
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().approve(this) />
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().reject(this) />
</cffunction>
<!--- State-related methods --->
<cffunction name="createStateNameMappings" access="private" returntype="void" output="false" hint="">
<cfset var local = structNew() />
<cfset variables.stateMappings['draft'] = variables.instance.draftState />
<cfset variables.stateMappings['review'] = variables.instance.reviewState />
<cfset variables.stateMappings['publishApproval'] = variables.instance.approvePublishState />
<cfset variables.stateMappings['published'] = variables.instance.publishedState />
</cffunction>
<cffunction name="setStateByName" access="public" returntype="void" output="false" hint="">
<cfargument name="stateName" type="string" required="true" />
<cfset setCurrentState(getStateByName(arguments.stateName)) />
</cffunction>
<cffunction name="getStateByName" access="private" returntype="AbstractContentState" output="false" hint="">
<cfargument name="stateName" type="string" required="true" />
<cftry>
<cfreturn variables.stateMappings[arguments.stateName] />
<cfcatch type="any">
<cfthrow message="No state with a mapping of '#arguments.stateName#' could be found." />
</cfcatch>
</cftry>
</cffunction>
<cffunction name="getCurrentState" access="private" returntype="AbstractContentState" output="false" roles="" hint="I return the currentState.">
<cfreturn variables.instance.currentState />
</cffunction>
<cffunction name="setCurrentState" access="private" returntype="void" output="false" roles="" hint="I set the currentState.">
<cfargument name="currentState" type="AbstractContentState" required="true" hint="currentState" />
<cfset variables.instance.currentState = arguments.currentState />
</cffunction>
</cfcomponent>
Listing F
<cfcomponent output="false">
<cffunction name="init" access="public" returntype="AbstractContentState" hint="Constructor.">
<cfargument name="stateName" type="string" required="true" />
<cfset setStateName(arguments.stateName) />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the save method." />
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the approve method." />
</cffunction>
<cffunction name="deploy" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the deploy method." />
</cffunction>
<cffunction name="makeDraft" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the makeDraft method." />
</cffunction>
<cffunction name="getStateName" access="private" returntype="string" output="false" roles="" hint="I return the stateName.">
<cfreturn variables.instance.stateName />
</cffunction>
<cffunction name="setStateName" access="private" returntype="void" output="false" roles="" hint="I set the stateName.">
<cfargument name="stateName" type="string" required="true" hint="stateName" />
<cfset variables.instance.stateName = arguments.stateName />
</cffunction>
</cfcomponent>
<cfcomponent name="DraftState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">
<cffunction name="init" access="public" returntype="DraftState" hint="Constructor.">
<cfset super.init('Draft') />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' saving content item...<br/>
</cfoutput>
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('review') />
</cffunction>
</cfcomponent>
<cfcomponent name="ReviewState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">
<cffunction name="init" access="public" returntype="ReviewState" hint="Constructor.">
<cfset super.init('Review') />
<cfreturn this />
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting content author that content is approved...<br/>
State '#getStateName()#' marking content as ready for publish approval...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('publishApproval') />
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting content author that content is rejected...<br/>
State '#getStateName()#' setting back to draft mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('draft') />
</cffunction>
</cfcomponent>
<cfcomponent name="ApprovePublishState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">
<cffunction name="init" access="public" returntype="ApprovePublishState" hint="Constructor.">
<cfset super.init('ApprovePublish') />
<cfreturn this />
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' Marking content as deployed...<br/>
State '#getStateName()#' Pushing live and updating content cache...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('published') />
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting reviewer that publishing is rejected...<br/>
State '#getStateName()#' setting back to review mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('review') />
</cffunction>
</cfcomponent>
<cfcomponent name="PublishedState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">
<cffunction name="init" access="public" returntype="PublishedState" hint="Constructor.">
<cfset super.init('Published') />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' setting the content to draft mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('draft') />
</cffunction>
</cfcomponent>
Listing G
<h2>Now, using the State pattern:</h2>
<cfset draftState = createObject('component','DraftState').init() />
<cfset reviewState = createObject('component','ReviewState').init() />
<cfset approvePublishState = createObject('component','ApprovePublishState').init() />
<cfset publishedState = createObject('component','PublishedState').init() />
States created...<br/>
<hr/>
<cfset content = createObject('component','ContentItemContext').init(draftState,reviewState,approvePublishState,publishedState,'draft') />
Context created in <strong>draft</strong>...<br/>
<cfset content.save() />
<cfset content.approve() />
<cfset content.approve() />
<cfset content.approve() />
<cfset content.save() />
<cfset content.approve() />
<cfset content.reject() />
<hr/>
<cfset content2 = createObject('component','ContentItemContext').init(draftState,reviewState,approvePublishState,publishedState,'review') />
Context created in <strong>review</strong>...<br/>
<cfset content2.reject() />
<cfset content2.save() />
<cfset content2.approve() />
<cfset content2.approve() />
<cfset content2.reject() />
What happens is that the ContentItemContext passes a reference to itself ("this") into each of the States when it calls them. In this way, a State CFC can perform its behavior and then can instruct the ContentItemContext to move to the next state.
If we need to add more states, we just need to create a new State object, along with a bit of code to let the ContentItemContext become aware of the new State. The behavior for any future states is encapsulated, so we avoid changing existing code as much as possible.
Also, in most cases, the States can be singleton objects, meaning they contain no instance data and they need to be created only once. We might need many ContentItemContext CFCs (one for each content item), but they can all use the same State instances. This is nice because it reduces the number of CFCs we have to create. This isn't always the case, but it often is.
I've attached a UML diagram showing the State pattern in Figure A. For people already familiar with patterns, you might notice that the object relationships are similar to the Strategy Pattern and the Bridge Pattern. While the relationships are the same, the intent of each of the patterns is different.
Figure A |
![]() |
| State pattern in a UML diagram |
Worth it
I've shown how the State pattern can be used to solve the problem of method or conditional explosions in a CFC. While the use of patterns can introduce its own type of complexity to a system, the benefits are often worth it. This is especially true when considering future maintenance and changes. If you have any questions or comments, feel free to post to the discussion forum at the bottom of this page or contact me at my Blog.
Print/View all Posts Comments on this article
SponsoredWhite Papers, Webcasts, and Downloads
- Sprint IPVoice Connect Fact Sheet Sprint
- Demo: Need Disk Space? IBM DB2 9 Compression Demo IBM
- Sprint DataLink for Wireless WAN Fact Sheet Sprint
- Nextel Direct Connect Fact Sheet Sprint
- Microsoft SQL Server 2005: Deployment and Tests in an iSCSI SAN Dell EqualLogic
Article Categories
- Security
- Security Solutions, IT Locksmith
- Networking and Communications
- E-mail Administration NetNote, Cisco Routers and Switches
- CIO and IT Management
- Project Management, CIO Issues, Strategies that Scale
- Desktops, Laptops & OS
- Windows 2000 Professional, Microsoft Word, Microsoft Excel, Microsoft Access, Windows XP,
- Data Management
- Oracle, SQL Server
- Servers
- Windows NT, Linux NetNote, Windows Server 2003
- Career Development
- Geek Trivia
- Software/Web Development
- Web Development Zone, Visual Basic, .NET


