Command Pattern [Part 1]
Well this is i think the most common pattern that i use in my apps
it’s not the exact implementation but it works for me =D
here’s and example on how i use it
Problem:
You have a Menu Instance and each Menu Item needs to execute different actions
Solution
the fastest way
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | package { import flash.display.MovieClip; import flash.events.MouseEvent; import flash.net.navigateToURL; import flash.net.URLRequest; import flash.external.ExternalInterface; import com.grupow.controls.WButton; /** * @author Raúl Uranga */ public class Main extends MovieClip { private var container:MovieClip; private var _items:Array; public function Main() { _items = new Array(); container = new MovieClip(); var labels:Array = ["go to grupow.com", "go to google.com", "openPopUp"]; for (var i:int = 0; i < 3; i++) { var item:WButton= new WButton(); item.label = labels[i]; item.y = i * 20; item.addEventListener(MouseEvent.CLICK, click_handler, false, 0, true); _items.push(item); container.addChild(item); } addChild(container); } private function click_handler(e:MouseEvent):void { switch (e.target) { case _items[0] : navigateToURL(new URLRequest("http://www.grupow.com"), "_blank"); break case _items[1] : navigateToURL(new URLRequest("http://www.google.com"), "_blank"); break case _items[2] : if (ExternalInterface.available) { ExternalInterface.call("popUp", "http://www.vincehuston.org/dp/behavioral_rules.html", "Rules of thumb", 500, 700, 1,1); } break } } } } |
this is the easiest and fastest way, but that doesn’t mean it is the correct way…
what if i need to add another behavior??
what if instead of having 3 menu items the menu grows to 20 or 30 items?
the switch statement will go crazy and becomes too hard to understand and read!
Solution: The Command Pattern =D
lets get our hands dirty
first we create the interface
1 2 3 4 5 6 7 8 9 10 | package com.grupow.commands { /** * @author Raúl Uranga */ public interface ICommand { function execute():void; } } |
then we separate the execution code and convert it in to Classes that implement the ICommand Interface
PopupCommand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com.grupow.commands { import flash.external.ExternalInterface; /** * @author Raúl Uranga */ public class PopupCommand implements ICommand { private var url:String; private var title:String; private var width:Number; private var height:Number; private var resizable:Number; private var scrollbars:Number; public function PopupCommand(url:String, title:String, width:Number, height:Number, resizable:Number, scrollbars:Number) { this.url = url; this.title = title; this.width = width; this.height = height; this.resizable = resizable; this.scrollbars = scrollbars; } /* INTERFACE com.grupow.commands.ICommand */ public function execute():void { if (ExternalInterface.available) { ExternalInterface.call("popUp", this.url, this.title, this.width, this.height, this.resizable, this.scrollbars); } } } } |
GetURLCommand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package com.grupow.commands { import flash.net.navigateToURL; import flash.net.URLRequest; /** * @author Raúl Uranga */ public class GetURLCommand implements ICommand { private var url:String; private var window:String; public function GetURLCommand(url:String = null, window:String = null) { this.url = url; this.window = window; } /* INTERFACE com.grupow.commands.ICommand */ public function execute():void { navigateToURL(new URLRequest(url), window); } } } |
it’s a good practice to use NullObjects to avoid Null Instances
NullCommand
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.grupow.commands { /** * @author Raúl Uranga */ public class NullCommand implements ICommand { public function NullCommand() { } /* INTERFACE com.grupow.commands.ICommand */ public function execute():void { //null } } } |
ok Raúl, nice……… you just create a bunch of classes! so what???
well here’s the implementation…
-our new Button:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.grupow.display { import flash.display.MovieClip; import com.grupow.controls.WButton import com.grupow.commands.ICommand; import com.grupow.commands.NullCommand; /** * ... * @author Raúl Uranga */ public class CommandButton extends WButton { public var command:ICommand; public function CommandButton() { super(); command = new NullCommand(); } public function execute():void { command.execute(); } } } |
notice that we initializated the command variable with a NullCommand Object
so we don’t have to worry about Null References Erros
the Main App becomes to……..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | package { import flash.display.MovieClip; import flash.events.MouseEvent; import com.grupow.commands.*; import com.grupow.display.CommandButton; /** * @author Raúl Uranga */ public class Main extends MovieClip { private var container:MovieClip; private var _items:Array; public function Main() { _items = new Array(); container = new MovieClip(); var labels:Array = ["go to grupow.com", "go to google.com", "openPopUp"]; var commands:Array = [new GetURLCommand("http://www.grupow.com", "_blank"), new GetURLCommand("http://www.google.com", "_blank"), new PopupCommand("http://www.vincehuston.org/dp/behavioral_rules.html", "Rules of thumb" , 500, 700, 1, 1)]; for (var i:int = 0; i < 3; i++) { var item:CommandButton = new CommandButton(); item.label = labels[i]; item.command = commands[i] as ICommand; item.y = i * 20; _items.push(item); container.addChild(item); } container.addEventListener(MouseEvent.CLICK, click_handler, false, 0, true); addChild(container); } private function click_handler(e:MouseEvent):void { if (e.target is CommandButton) { e.target.execute(); } } } } |
the cool thing is that i don´t have to worry about wich button was clicked!
so we repleace something like this
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | private function click_handler(e:MouseEvent):void { switch (e.target) { case _items[0] : navigateToURL(new URLRequest("http://www.grupow.com"), "_blank"); break case _items[1] : navigateToURL(new URLRequest("http://www.google.com"), "_blank"); break case _items[2] : if (ExternalInterface.available) { ExternalInterface.call("popUp", "http://www.vincehuston.org/dp/behavioral_rules.htmll", "Rules of thumb", 500, 700, 1,1); } break } } |
in to this!!!!!!!
45 46 47 48 49 50 | private function click_handler(e:MouseEvent):void { if (e.target is CommandButton) { e.target.execute(); } } |
and……..
-we separate the execution code
-we remove duplicated code
-we can reuse Objects and make them switchable
-code is more human readable
-if you need to add another behavior you just create another Command Class (this is part 2… coming soon)
obviously this way of doing it consumes more time but at the end it has more benefits.
cheers!
hi! thanks for sharing!
i don’t get why the button should also implement the icommand interface.
Can’t you just store a reference to the command in the button, and call its execute method when needed?
sitron
4 Mar 09 at 16:01
for me is just to know that has an execute method but in second thought you are right, the Button Class actually doesn’t need to implement the ICommand interface so we should change the click handler method to:
private function click_handler(e:MouseEvent):void
{
if (e.target is CommandButton) {
e.target.execute();
}
}
and remove the interface implementation from the ButtonClass
raúl
4 Mar 09 at 18:31
Is there any point to picking up the Click Event on the ‘container’ MovieClip and within the Main class rather than form within the CommandButton class? Wouldn’t that promote better encapsulation?
Judit
28 Jun 09 at 18:17
Hi Judit!
Well i’m doing in this way because and old habit that i have =)
what if the Main Class is a “Menu Manager Class”
and this Class needs to know what item was clicked
for example:
package {
public class MenuManagerClass
{
public function addItem(value:Object):CommandButton
{
var item:CommandButton = new CommandButton();
item.label = value.label;
item.addEventListener(MouseEvent.CLICK, click_handler, false, 0, true);
_items.push(item);
.
.
.
}
public function getMenuItemAt(index:int):CommandButton
{
……..
}
public function activeMenuItem(value:CommandButton):void
{
if(_selectedItem != value) {
if(_selectedItem != null ) {
_previousItem = _selectedItem
previousItem .deactive();
}
_selectedItem = e.target;
_selectedItem.deactive();
_selectedItem.execute();
this.dispatchEvent(new Event(Event.CHANGE));
}
}
private function click_handler(e:MouseEvent):void
{
activeMenuItem(e.target);
}
public function get selectedItem():CommandButton
{
return _selectedItem;
}
.
.
.
.
.
you can use this way not just for active and deactive buttons
you can do a bunch of other stuff there
but i hope you can get the idea =)
Cheers.
raúl
29 Jun 09 at 10:25
the beauty of programing is that you can do things in so diferent ways
in the MenuManager Example
yes, you can encapsulate the click_handler in the CommandButton Class and pass a reference to Menu Manager and then you call manager.activeMenuItem(this); inside the handler
so, you can do it the best way it fits your project
raúl
29 Jun 09 at 16:03