The Strategy Design Pattern in ColdFusion
Thoughts on the Strategy design pattern specific to ColdFusion and it's typeless nature.
What is the Strategy design pattern?
Suppose you have an object A which needs to use another object B.
[A] --- uses ---> [B]
Then a different situation requires that you need to exchange B for another object C; so object A is then making use of C.
[A] --- uses ---> [C]
The ability to exchange objects B and C without changing object A is the Strategy design pattern in action.
In order for this to be possible, objects B and C need to both implement the common set of functions that object A requires.
In this description, the object A is called our context. The objects B anc C are our strategy objects.
What problem does the Strategy pattern solve?
The Strategy pattern makes it easy to change the behaviour of an algorithm used by the context component. As long as any new strategy component includes the common set of functions required, it may be used within the context component.
Example: Form field validation
Suppose that you have a form that has three text fields. One of the fields is a name that may be up to 50 characters, then second field is an email address, and the third field is a number. We need to provide code to validate these fields.
We can consider the following components may be used here:
- TextField - holds the data entered by the user
- LengthValidator - knows how to check the length of a string
- EmailValidator - knows how to check if a string is an email address
- NumberValidator - knows how to check if a string is numeric
In this example, the TextField is our context object. The other three are our strategy objects. In order for our strategy objects to be useful, they must all share a common set of functions. In this case they all share one function:
<cffunction name="validate" returntype="boolean" output="false">
<cfargument name="text" type="string" required="true">
<!--- Implement function here --->
</cffunction>
Let's look at each of our objects in more detail:
TextField
init(text) - Sets the initial text in the field.
addValidator(validator) - Adds a validator that must be applied to the field.
isValid() - Returns true if all of the added validators return true.
LengthValidator
init(minLength,maxLength) - Initialiser to specify the min and max lengths permitted.
validate(text) - Returns true if the length of the text is within the initialiser range.
EmailValidator
init() - Initialiser does nothing.
validate(text) - Returns true if the provided text is an email address.
NumberValidator
init() - Initialiser does nothing.
validate(text) - Returns true if the provided text is an a number.
In this example, the strategy object is added to the context via an 'addValidator' function. If the context object only requires a single strategy it could be supplied via the init() function.
Now some code to see how the TextField might be coded
<cfcomponent output="false">
<cfset variables.text = "">
<cfset variables.validators = arrayNew(1)>
<!--- Initialise our text field with some text --->
<cffunction name="init" output="false">
<cfargument name="text" type="string" required="true">
<cfset variables.text = arguments.text>
<cfreturn this>
</cffunction>
<!--- Add any kind of validator to our field --->
<cffunction name="addValidator" output="false">
<!--- Notice type="any" here to allow any kind of validator --->
<cfargument name="validator" type="any" required="true">
<cfset arrayAppend(variables.validators, arguments.validator)>
</cffunction>
<!--- Loop through each validator and apply to the stored text data --->
<cffunction name="isValid" output="false">
<cfset var i = "">
<cfset validator = "">
<cfloop index="i" from="1" to="#arrayLen(variables.validators)#">
<cfset validator = variables.validators[i]>
<!--- We require that the validator has one function called 'validate'
that knows how to validate a text string --->
<cfif not validator.validate(variables.text)>
<cfreturn false>
</cfif>
</cfloop>
<cfreturn true>
</cffunction>
</cfcomponent>
Let's look at an example of how one of the validators might be implemented:
<cfcomponent output="false">
<cfset variables.min = "">
<cfset variables.max = "">
<!--- Initialise our min and max values --->
<cffunction name="init" output="false">
<cfargument name="min" type="numeric" required="true">
<cfargument name="max" type="numeric" required="true">
<cfset variables.min = arguments.min>
<cfset variables.max = arguments.max>
<cfreturn this>
</cffunction>
<!--- Return true if the length of the supplied text is within the range --->
<cffunction name="validate" output="false" returntype="boolean">
<cfargument name="text" type="string" required="true">
<cfif len(arguments.text) ge variables.min
and len(arguments.text) le variables.max>
<cfreturn true>
</cfif>
<cfreturn false>
</cffunction>
</cfcomponent>
Now let's see how our components might be used together:
<cfset lengthValidator = createObject("component","LengthValidator").init(1,50)>
<cfset emailValidator = createObject("component","EmailValidator").init()>
<cfset numberValidator = createObject("component","NumberValidator").init()>
<!--- Create our text fields. We hard code the initial text for now,
but in a real application this data would come from a form submitted by the user --->
<cfset nameField = createObject("component","TextField").init("Jim Boss")>
<cfset emailField = createObject("component","TextField").init("jimboss.com")>
<cfset numField = createObject("component","TextField").init("99")>
<!--- Now add the validators to the fields --->
<cfset nameField.addValidator(lengthValidator)>
<cfset emailField.addValidator(emailValidator)>
<cfset numField.addValidator(numberValidator)>
<!--- Now check if our fields are valid --->
<cfoutput>
#nameField.isValid()#<br />
#emailField.isValid()#<br />
#numField.isValid()#<br />
</cfoutput>
This would display the output
true
false
true
Example: Sorting Objects
This example demonstrates separating out a sorting algorithm from a collection of objects.
<cfset names = createObject("component","NameCollection").init()>
<!--- Add some strings to the collection --->
<cfset names.add("Tim","Burton")>
<cfset names.add("Peter","Jackson")>
<cfset names.add("David","Lynch")>
<cfset names.add("David","Cronenberg")>
<!--- Sort the collection by first name --->
<cfset sortStrategy = createObject("component","FirstNameSort").init()>
<cfset names.setSortStrategy(sortStrategy)>
<cfset names.sort()>
<!--- Sort the collection by last name --->
<cfset sortStrategy = createObject("component","LastNameSort").init()>
<cfset names.setSortStrategy(sortStrategy)>
<cfset names.sort()>
In this example, the NameCollection is our context component, and the FirstNameSort and LastNameSort are our strategy components.
How to identify the Strategy design pattern
Couple of notes about how to identifiy the Strategy design pattern:
- There must be a context object
- There must be one or more strategy objects that have a shared set of common functions
- The strategy objects are stored inside the context object and are supplied either via the init() function or via a setStrategy() type function.
The Strategy pattern in ColdFusion vs other languages
Descriptions of the Strategy pattern in some other languages (such as Java) will usually refer to interfaces. An interface is used to define the common set of functions that all of the strategy objects use. Due to ColdFusion's typeless nature and runtime checking of types, interfaces are not very relevant in the language so I have not included them here.

Yes, for typical day to day work with hand coded forms all of this would be overkill. The original idea behind the example was around developing a system that allows forms to be created by end users (via a form building user interface) then this technique would become more useful. I notice I didn't mention that!
Would be good to come up with a more common practical example.
Since you showed an example using validation I am just wondering what would be the best approach extending your example to return messages to the user when something is invalid instead of only returning the true or false flag. In some cases a single form field/item may require more than one validation type with different constraints.
If you were validating an "EmailAddress" form item the user would need to know the following if a failure occurred:
1)The level of the failure (e.g.: warn, error)
2)Which Form item had the failure (e.g.: Email Address)
3)The message describing the failure derived from the type of validation used
(
e.g.:
The "E-Mail Address" is required... [RequiredStringValidator]
or
The E-Mail Address provided is not a valid e-mail address... [EmailAddressValidator]
)
Also, at what level within ones architecture should this be implemented? Should it be done at the main Service Level or Before the main Service is invoked?
I am just trying to decide on a practical way to lay these things out without stepping all over each other. I have a project that requires a great deal of validation, including simple form validation to complex data validation.
Thanks,
Hussein
Couple of thoughts for you (considering your comments).
EXTENDING THE EXAMPLE
Firstly, all of this depends on each form field being an object itself that can have validators added to it. For most situations I imagine this would not be the case.
The validation examples here are pretty much "context independant" so error messages, if any, would need to be correspondingly generic, e.g.
- "Invalid date"
- "Must be a number from 1 to 10"
- "Must be 30 characters or less"
So some additional components I might create would be:
ValidationResult
init(formField,level,message)
getFormField()
getLevel()
getMessage()
getLevel() returns something like "ok", "warning" or "error"
ValidationResultCollection
init()
add(validationResult)
getArray() - returns an array of ValidationResults
(or a getIterator() function)
I might create a new base Validator component/interface that all validators would extend/implement. This would just contain some helper functions.
Validator
validate(formField)
validate() needs to be implemented in the subclass and returns a ValidationResult object. So the subclass is specifying the error level and message.
Something I omitted from the example above was a base FormField component that needs to be extended by each form field compoenent:
FormField
addValidator(validator)
getValue()
validate()
addValidator() would be called multiple times to support multiple validators for one field.
getValue() needs to be overridden by the subclass to return the current value of the field. In the FormField class this would just throw a "NotImplemented" exception.
validate() would loop through the validator components passing calling validate(this). It would build up a ValidationResultCollection and return it.
WHERE DOES VALIDATION BELONG?
You may have noticed that validation discussions regularly come up and there is always a good variety of answers. I am still thinking this through, however I have a bookmarked thread on this that I found very interesting, particularly an entry by Jamie Metcher:
http://www.mail-archive.com/cfcdev@cfczone.org/msg...
As I see it, validation is part of the business logic so server side validation is best placed in the service layer and beyond (and not in the controller, for example).
Not sure if any of this helps you solve your particular cases but is a good discussion.
Thank you very much. Your view on this topic is very interesting and yes, it was helpful. I agree that validation should be handled at the service level and beyond, makes sense to me.
Thanks again.
Hussein