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.

source

cheers!

This entry was posted in General. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

5 Comments

  1. sitron
    Posted 4 March 2009 at 16:01 | Permalink

    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?

  2. raúl
    Posted 4 March 2009 at 18:31 | Permalink

    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

  3. Judit
    Posted 28 June 2009 at 18:17 | Permalink

    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?

  4. raúl
    Posted 29 June 2009 at 10:25 | Permalink

    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.

  5. raúl
    Posted 29 June 2009 at 16:03 | Permalink

    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

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*