Welcome to my tutorial on the Command Pattern of Object Oriented Programming, Coldfusion MVC (model, view, control) style. This tutorial will assume that the reader knows how to program in Coldfusion including components and has a basic understanding of OOP. On a personal note I would like to make a comment to non-advanced programmers about Object Oriented Programming. Don't do it unless it's absolutely neccessary. Especially small to medium size web sites, they just don't need it. However, learning OOP is certainly not a waste of time and you may even use it one day on a very large complexed mission critical application. Until then, practice makes perfect.
Defined - The Command Pattern:
Encapsulating a request as an object, thereby letting other objects with different request, que or log request, and support undoable operations.
We'll start by describing a situation in which the Command Pattern may be used. Imagine designing a program for a remote control to operate different appliances around the house. Turning lights and steroes off and on with a simple switch of the remote control. We'll be building a program for the controller, not the appliances. We'll assume that the appliances are ready made and programmed to receive commands. These commands are pre-programmed with their own methods that we must invoke to work them.
Example appliance program (simplified):
LivingRoomLight{
on(){
return "Living Room light is on.";
}
off(){
return "Living Room light is off.";
}
dim(){
return "Living Room light dimmed.";
}
}
Don't concern yourself with the code above to much. We'll be making our own, this is just a simple example for the discussion.
Access the finished remote control here.
Or download the source code here.
The example appliance above is one of many and each has it's own methods of working it's various gizmos. Another appliance can have methods like setVolume() or setTemperature(). That being said our remote needs to be oblivious to how a gizmo works, it only needs to know what appliance to control and what methods to execute. Our remote control has slots that can be assigned to different appliances at execution time. Each time you add a slot that slot becomes a control object and you program in what appliance it works and what the buttons do (what methods it invokes). This will decouple our remote from the appliances. The remote control gives the command object the command, the command object takes the command to the appliance, and the appliance complies with the command. The command will encapsulate the method(s) for the appliance to comply with.
Lets talk code now without the remote control example. What exactly does all this mean? The controlling object is called a Client that is instantiated when the program starts. During the instantiation it creates, according to its code, Command objects and its commands and receivers. When the command objects are created they are set within an invoke object called the Invoker. The client, by way of your interaction, will call upon this invoker to execute whatever command you want to the appropriate receiving object called the Receiver. The receiver will know what to do with the command.
Our Command Object:
We won't try to figure it all out before we build. We'll build things from the bottom up to help grasp the entire concept starting with our command object. Our command object does one thing and that is executes. A specific command object will extend this component for it's own execution method.
Command.cfc
This is what's know in Java as an interface. You may ask later why we use this, especially since specific command objects have thier own execute method. Well, you don't have to use it but now we have a global command object we can change as needed down the road if needed and this modulerizes our code into parts and pieces for future development changes, additions, and deletions.
You'll notice heavy use of the 'init' function in my coding. This is to replicate Java's equivilant to the constructor, which Coldfusion components do not have. A component is or can be considered a class, and in Java classes among others the constructor gets executed when the class is used. With Coldfusion you'll have to execute the 'init' function manually when the component is instantiated, you'll see how later. This command component will not be used directly so it is not important to understand it in detail other than it is sometimes a good idea to make an interface for possibly commonly used functions of many objects. The return type of the 'init' function is the component itself using the name of the component. This will return to it's caller all the functions available to this component. 'this' is not neccessary in front of .execute but I use it anyways. However 'this' in the return is neccessary. We're also going to make the execute function private because it does not need to be accessed outside of it's own component container.
<cfcomponent displayname="Command" hint="I am an interface to execute commands, I am implimented by specific command objects"> <cffunction name="init" access="public" returntype="Command"> <cfscript> this.execute = execute(); </cfscript> <cfreturn this /> </cffunction> <cffunction name="execute" access="private" returntype="void"> <!--- Overide by specific command objects ---> </cffunction> </cfcomponent> |
LightOnCommand.cfc
This is a "real" command oject extending the above Command.cfc. You see it also has an execute function. It's execute function will override Command.cfc's execute function using the extends="Command" directive. The 'init', known as the constructor from here on, takes in the appliance object and assigns the object to a usable parameter by the command object. We simply call it light because this command object is only concerned with the lights of any appliances it knows. And now any functions of this command object can invoke any functions of the appliance object. But we're only going to use it's on() method because this is a LightsOnCommand. Later we'll build a LightsOffCommand object to invoke the appliance's off() method if it has one. Also, we made the execute function public because another object will need access to this method. So basically this command object will know what appliance to command and what commands to execute.
<cfcomponent displayname="LightOnCommand" hint="I am the command used to turn on lights" extends="Command"> <cffunction name="init" access="public" returntype="LightOnCommand"> <cfargument name="light" type="any" required="yes" /> <cfscript> this.light = light; </cfscript> <cfreturn this /> </cffunction> <cffunction name="execute" access="public" returntype="void"> <cfscript> this.light.on(); </cfscript> </cffunction> </cfcomponent> |
Our Remote Control (client) Object:
Now we have command objects set up and all we need is something to control these commands, enter the Remote Control. The Remote Control object, named SimpleRemoteControl here, has slots available for different appliances. And in these slots you will pass in the command objects you want assigned. It is important to understand how different programming paradigms handle OO programming. Such as Java and Flash (ActionScript) are basically real time whereas HTML or Coldfusion web pages are not. Since web pages act on states of a single request then the url or form parameters will have to suffice to pass in or state what appliance and button is pressed or clicked on. In contrast, a Flash program would have buttons that inact these already ready to use objects. It's easier to understand if you have experience with this and harder to explain, but moving on, just know that by links we are able to tell our objects what appliance and what action to take using conditional statements. Thus the need for dynamic languages instead of boring old HTML. More on this later.
SimpleRemoteControl.cfc
First in the constructor we set a slot variable to be used throughout the component, globaly. The command function accepts Command objects and assigns a slot to that object. Any commands of that command object will be controled through that slot. Also the remote only knows if it had a button pressed through the buttonWasPressed method. It will tell the slot to execute whatever command it is. Here is where the difference between stateful and stateless application comes into play. If this was a stateless application you would pass all available command objects to it and assign multiple slots as need ie. slot1, slot2, slot3 etc.. And then in the buttonWasPressed method you would need a conditional to tell which slot to execute. But we're not doing stateless programming here.
<cfcomponent displayname="SimpleRemoteControl" hint="I am a simple remote control"> <cffunction name="init" access="public" returntype="SimpleRemoteControl"> <cfset variables.slot = ""> <cfreturn this /> </cffunction> <cffunction name="setCommand" access="public" returntype="void"> <cfargument name="command" type="Command" required="yes" /> <cfscript> variables.slot = command; </cfscript> </cffunction> <cffunction name="buttonWasPressed" access="public" returntype="void"> <cfscript> variables.slot.execute(); </cfscript> </cffunction> </cfcomponent> |
And now we're ready to create the remote control interface to tie it all together and see this thing in action. But first I will show you my appliance code for the Living Room lights. This is assumed to be a vendor supplied program piece you are creating an API to interface with. You may create your own or use mine which just prints out "Lights on". I should further explain that my code does not return a string or anything to output to the string, but rather sets a string to a request scope accessable to the remote control interface. Just in case your wondering.
You'll notice in my coding that I added a test for request.message. This is for later use with multiple commands in 'All Lights On' and 'Party Mode'. Everything here before Continuing will work without it. And yes, my coding is copied and pasted from the working source.
LivingRoomLight.cfc
<cfcomponent displayname="Light" hint="I am a light fixture with methods"> <cffunction name="init" access="public" returntype="LivingRoomLight"> <cfreturn this /> </cffunction> <cffunction name="on" access="public" returntype="void"> <cfscript> request.message = "<br />Living Room light is on"; </cfscript> </cffunction> </cfcomponent> |
remotecontrol.cfm
Not in it's entirity I included the important script part here. You may use cfinvoke or whatever means neccessary for you. You'll note that the script is passed a url parameter, 'action', that has it's own command to use in a conditional to tell the remote control object what command to execute and that it's button was pressed. Also note the path to my component, yours may be different or you can set up the model folder and create a mapping to it like I have.
<cfscript>
myRemote = createObject("component","model.remotecontrol.SimpleRemoteControl").init();
if(isDefined("url.action") and url.action eq "livingRoomLightsOn"){
livingRoomLights = createObject("component","model.receivers.LivingRoomLight").init();
livingRoomLightsOn = createObject("component","model.remotecontrol.LightOnCommand").init(livingRoomLights);
myRemote.setCommand(livingRoomLightsOn);
myRemote.buttonWasPressed();
}
</cfscript>
|
I'll try to summerize the steps the code takes:
Now we're ready to create other appliance classes as needed and we can add on more appliances as they become available. For each new appliance only another command object needs to be created, assuming the appliance object is supplied by the vendor or appliance maker. See how the actual remote control object never needs tending to with appliance changes, Object Oriented Programming rocks. As for your remote control interface, if you program it correctly it never needs changing either except that the button or link needs added or changed. I'll add the command object to turn the light off and leave the rest to you. By the way I add the off() method to our vendor supplied appliance object. Remember that this would have been there since the beginning but I left it off earlier to shorten the code examples. Plus I changed my interface conditional to use switch statements because I can. Just concentrate on the OOP and make your own mind up to its implimentation.
LightOffCommand.cfc
Notice the similarity to the LightOnCommand. All I had to do was change the on words to off and save it under a different name.
<cfcomponent displayname="LightOffCommand" hint="I am the command used to turn off lights" extends="Command"> |
And now that it saved and we add the link to the off button for the Living Room Lights slot in our remote control interface.
Here is my interface switch script so far:
<cfscript> |
And then I added kitchen light commands because I just purched remote control kitchen lights. In reality the company that makes the appliances and the company that makes the remote would have gotten together and had all this done selling the stuff seperately already programmed. And to not get confused with other living room lights an FCC control number could be used instead. But it can be assumed the remote is upgradable via the Internet by simply downloading the new instructions. Good idea huh?
Any ways, my kitchen light appliance object is similar to the living room light object accept for a few changes. And I've further upgraded my interface switch statement below:
<cfscript> |
Notice the reuse of the LightOnCommand above, OOP again to the rescue. Now all light appliances can use the same LightOnCommand/LightOffCommand. Can you see the possibility with a dimmer switch? I'll leave this up to your imagination.
This is all easy enough so far right? Now lets add some more easy stuff, multiple commands and an undo button. Can you guess where the undo method goes? As it happens my universal remote control has a feature where I can control the off/on button to switch multiple appliances if held down longer. I want this remote control to do the same only we'll preprogram what multiple appliances get the all off/on command. Unfortunatly it doesn't have an undo button but this is not an ordinary remote control.
SimpleRemoteControl.cfc (changed):
Not so simple any more but try to follow. We're now using onCommands and offCommands to represent command slots as an array. Plus we've no included a NoCommand object (a Null Object useful in many patterns and also concidered a design pattern itself) to handle buttons pressed without a class to point to. So in our constructor we're going to go ahead and run through all the possible slots and assign them to NoCommand, just in case. This will get overriden during the setCommand method if there is an actual command object to take a slot. Now the remotecontrol's setCommand gets called for each and every appliance at execution time and is passed the appliances on and off command and the slot number it would like to be assigned to, 1 thru 7 in this case. Of course this is a brick-and-mortor remote control and we know that there are only 7 slots physically available. We also seperated the buttonWasPressed method to on/off states to match the on/off command arrays.
<cfcomponent displayname="SimpleRemoteControl" hint="I am a simple remote control"> |
remotecontrol.cfm
So far we haven't had to change the appliances classes or the command classes but we do need to do a lot of work on the interface. Keep in mind that we are building the prototype here and until it is perfect we are going to make some changes. So here is the new script piece from the implimentation code below. We haven't coded the allLightsOn/Off or partyModeOn/Off yet but it'll work because of the NoCommand class we added. And we'll add undo last. Normally I would be seperating this to it's own components, possibly using a factory pattern or something else but not this time.
<cfscript> |
Let's get off track for a moment and look at something a little different. We're not going to impliment this but let's suppose the remote control had more buttons for the stereo, and you could use this for the dimmer on lights mentioned earlier. Let's add some stereo functions like setCD() and setVolume(). This is simply more command objects with a twist.
StereoOnWithCDCommand.cfc
We want the stereo to automatically turn on if the CD button is pressed right? Now we need a command object to do both turn on the stereo and set the CD. Also we'll add in setting the volume at the same time too just for demonstration.
See how we're using the stereo class already created before. We'll assume for this example that the new methods were added to the stereo class too.
<cfcomponent displayname="StereoOnWithCDCommand" extends="Command"> |
MacroCommand.cfc
We'll simply create a macro command to handle multiple request to multiple appliance objects. This one macro command will control multiple other commands using an array of commands. The execute of this command will loop throught the other commands passed to it in an array and execute their execute method, one by one.
<cfcomponent displayname="MacroCommand" hint="I am the command used to control other macros" extends="Command"> |
remotecontrol.cfm (addition change)
And the interface needs to be changed with the following addition. Basically your creating an array of commands for each macro you'll use. The array of commands is passed to the MacroCommand object which is then handed to the setCommand method of the myRemote object. And we see now also how one command object can be used multiple times for special commands that handle multiple other commands.
lightsOn = arrayNew(1); |
Command.cfc
First we'll need to change our command interface to add the new action. From this change you should be able to figure out where undo will also be implimented.
<cfcomponent displayname="Command" hint="I am an interface to execute commands, I am implimented by specific command objects"> |
Specific Command Classes
That's right, all our specific command classes need an addition. And undo method that's the opposite of it's main task. If the command is supposed to turn on a light, undo will turn it off. I'll do one for an example and you can change the rest.
<cfcomponent displayname="LightOnCommand" hint="I am the command used to turn on lights" extends="Command"> |
<cfcomponent displayname="LightOffCommand" hint="I am the command used to turn off lights" extends="Command"> |
SimpleRemoteControl.cfc
And of course we need a way to track the commands to undo in the main remotecontrol class. Without further ado here is the code. I will not explain it here because it does not work in our stateful Internet application because the memory of the undo command is not remembered from one page to the next. If this was programmed in Java or Flash it would work. I present the alternative below this.
<cfcomponent displayname="SimpleRemoteControl" hint="I am a simple remote control"> |
SimpleRemoteControl.cfc (Coldfusion alternative)
First of all you don't need the undoButtonWasPressed, we'll let the remote implimentation execute this itself. Then we'll use the session scope to hold undo memory. This isn't meant to be the sort of undo your used to, it's only an undo one step back. You could on your own create a mechanism to store multiple undos and go back x number of time maximum. I'll leave it up to you to do this if you want. Now each time a button is pressed the session.undoCommand gets reassigned to the command object being used. The when undo is pressed it simply uses that command object's undo method.
<cfcomponent displayname="SimpleRemoteControl" hint="I am a simple remote control"> |
remotecontrol.cfm
We'll leave the undo function up to the remotecontrol implimentation for now, maybe you have a better way and I'll be glad to change it if you send it to me, including a link reference to your site or email. Here is the changed piece from the script. Refer to the source code available for download if your still confused.
case "undo": |
That's it for me and I hope to have helped you out a little in understanding Coldfusion style OOP and this Design Pattern. If anything you should have learned a little more about components. Be sure to download the source code for futher study and leave a comment on my blog for this if you have any questions.
Thanks to Head First Design Patterns (O'Reilly Media, Inc.)