ActionScript 3.0 Design Pattern Re-use and Modification

Take This One to Work

Take This One to Work

Every Monday I have to separate out the paper, glass, aluminum, and plastic to recycle and take them up to the curb. This last Monday while engaged in this weekly ritual, I thought that recycling a design pattern might be an interesting exercise. Additionally, it’s the kind of design pattern advantage that can be used at work to save re-inventing the wheel when you have a similar development task. You may remember the Dragon Factory where we employed a Factory Method to set up an easy-to-use drag application. In the original Dragon Factory, both the Creator and Product participants had subclasses. However, with only a slight revision, it’s possible to create a little game for making different faces subclassing only the Product interface (abstract class). Figure 1 shows the File-Class Diagram:

<em><strong>Figure 1:</strong> Factory Method with Single Concrete Creator</em>

Figure 1: Factory Method with Single Concrete Creator

You can see the design similarities between this application (MrFace) and the Dragon application. The main difference is that this one has a single concrete factory (FaceCreator) for all of the concrete products. The factory churns out all of the Product children and puts them into an array. The Client requests the array from the Creator and pulls out the elements with a loop. As you can see, they’re all draggable elements. Try out the embedded SWF file below to see how it works:

MrFace

The same Product (from the Dragon application) is re-used so it is unnecessary to revise it. However, instead of a video and a dynamically generated graphic, all of the graphics were put into the Library as Sprite classes. (To make a Sprite class just change the Base class to flash.display.Sprite.)

The Abstract and Concrete Factories

Because the main difference between this application and the Dragon app is the way in which the factory classes (Creator and FaceCreator) handle products, we’ll start with them. First, the Creator class provides the necessary interface as shown in the following listing:

?View Code ACTIONSCRIPT
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
{
	//Mr Face App
	//Asbstract class--serves as interface
	//Creator
 
	import flash.errors.IllegalOperationError;
 
	public class Creator
	{
		private var dragNow:Array;
 
		public function createDragable():Array
		{
			dragNow=getDragon();
			return dragNow;
		}
 
		// ABSTRACT Method (must be overridden in a subclass)
		protected function getDragon():Array
		{
			throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
			return null;
		}
	}
}

If you’re thinking, That looks a lot like the Creator class for the Dragon application, you’d be absolutely right. In fact, the only difference is that this Creator returns an Array object instead of a Sprite. That’s the purpose of re-use.

The big change is in the way the ConcreteCreator (FaceCreator) works in this design pattern. The abstract function is getDragon(), and so it has to be overridden and set up to instantiate each of the Product children (facial parts) and stuff them into an array. That array is then returned when requested by the Client.

?View Code ACTIONSCRIPT
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
package 
{
	//Concrete Factory
 
	import flash.errors.IllegalOperationError;
 
	public class FaceCreator extends Creator
	{
		private var faceOn:Array=[];
 
		override protected function getDragon():Array
		{
			var ears:Product=new Ears();
			faceOn.push(ears);
 
			var ears2:Product=new Ears();
			faceOn.push(ears2);
 
			var glasses:Product=new Glasses();
			faceOn.push(glasses);
 
			var nose:Product=new Nose();
			faceOn.push(nose);
 
			var eyes:Product=new Eyes();
			faceOn.push(eyes);
 
			var mouth:Product=new Mouth();
			faceOn.push(mouth);
 
			var hair:Product=new Hair();
			faceOn.push(hair);
 
			var head:Product=new Head();
			faceOn.push(head);
 
			return faceOn;
		}
	}
}

By using an array, the Client only has to request a single object rather than all of the Product children individually as was the case in the Dragon application. That’s it as far as the factory classes are concerned. Next, in looking at the Product and its children classes, you will see that not much has changed from the original designs.

The Product and Its Brood

First of all, take a look at the Product class used as an interface for all of the children classes. Again, the Product is an abstract class instead of an Interface, and it re-uses the same methods and properties.

?View Code ACTIONSCRIPT
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
package 
{
	//Abstract class--serves as interface
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.errors.IllegalOperationError;
 
	public class Product extends Sprite
	{
		protected var base:Sprite=new Sprite();
 
		// ABSTRACT Method
		protected function placeParts(lr:uint,ud:uint):void
		{
			throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
		}
 
		protected function setPart(part:Sprite):void
		{
			addChild(base);
			base.addChild(part);
			base.addEventListener(MouseEvent.MOUSE_DOWN,dragPart);
			base.addEventListener(MouseEvent.MOUSE_UP,dropPart);
		}
 
		protected function dragPart(e:MouseEvent):void
		{
			this.startDrag();
		}
 
		protected function dropPart(e:MouseEvent):void
		{
			this.stopDrag();
		}
	}
}

There’s nothing new from the original in that Product class; so let’s move on to the concrete elements of the Product. The setup for this used Sprite classes stored in the Flash Library. (Creating a similar Library in Flash Builder should not be too difficult.) The setPart() method does most of the work, and the placeParts() method is overridden just to have an abstract method. (That was to demonstrate flexibility more than it was an essential function to override.)

Given that the abstract Product class is a workhorse, the child classes need only to call the Sprite class out of the Library. All Sprite classes end with a capital “S” to differentiate them from the Product subclasses themselves. For example, the Mouth class instantiates a MouthS class from the Library. Each of the face parts is a small subclass, and so all of them are placed together in the following listing:

?View Code ACTIONSCRIPT
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Ears extends Product
	{
		private var ears:Sprite = new EarS();
 
		public function Ears()
		{
			setPart(ears);
			placeParts(100,100);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}
 
//
 
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Eyes extends Product
	{
		private var eyes:Sprite = new EyesS();
 
		public function Eyes()
		{
			setPart(eyes);
			placeParts(100,150);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}
 
//
 
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Glasses extends Product
	{
		private var glasses:Sprite = new GlassesS();
 
		public function Glasses()
		{
			setPart(glasses);
			placeParts(80,170);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}
 
//
 
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Hair extends Product
	{
		private var hair:Sprite = new HairS();
 
		public function Hair()
		{
			setPart(hair);
			placeParts(100,200);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}
 
//
 
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Head extends Product
	{
		private var head:Sprite = new HeadS();
 
		public function Head()
		{
			setPart(head);
			placeParts(120,150);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}
 
//
 
package 
{
	//Concrete Product
	import flash.display.Sprite;
 
	public class Mouth extends Product
	{
		private var mouth:Sprite = new MouthS();
 
		public function Mouth()
		{
			setPart(mouth);
			placeParts(100,200);
		}
 
		override protected function placeParts(lr:uint,ud:uint):void
		{
			base.x=lr,base.y=ud;
		}
	}
}

Using Flash Builder (Flex), just place the different graphic elements into separate classes using the same Library class names. Just be sure to subclass them from a Sprite.

The Lazy Client

I’ve come to believe that the less the client has to do, the better. That’s because the Client really should do nothing more than make requests. In some designs the Client is actually part of the design and has more to do. However, as a rule of thumb, the Client should only make requests and place elements on the DisplayList. In this application, that’s all the Client does as you can see in the following listing:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package 
{
	import flash.display.Sprite;
 
	public class Client extends Sprite
	{
		private var faceParts:Array=[];
		private var myFace:Creator=new FaceCreator();
 
		public function Client()
		{
			faceParts=myFace.createDragable();
			var saveFace:uint=faceParts.length-1;
 
			for (var count:uint =0; count <=saveFace; count++)
			{
				addChild(faceParts.pop());
			}
		}
	}
}

As you can see, the Client just requests the Array from the Creator and then sorts out the array. Now that’s not a lot to do!

Stupid dog tricks: #1 and #2. Just in case you wondered, you can instantiate an array by using aName:Array=[] instead of aName:Array= new Array(). You can do the same thing with an Object using oName:Object={}. Maybe it isn’t as clear as spelling out the object name, but we’re all entitled to a few stupid dog tricks. Those are mine.

Why Take This to Work?

The whole idea behind design patterns is to generate re-usable code that contains good OOP. I realize that a drag-a-face-part game isn’t going to impress your boss (if your boss is infantile, it might) but that’s not important. What is important is to emphasize the fact that as your applications become more complex, you’re either going to have to re-start building applications with every new contract, which will probably include several hacks, make-dos, and god-awfuls. When your customer wants change, it will take time and effort and blow the stuffings out of the current application. So maybe with a simple little face, you can dispel the idea that design patterns will add to a workload. They’re just there to help.

Also, I think that anyone over the age of 5 can do better graphics than I can. So, I’ve provided the entire application including both CS3 and CS4 versions for you to add your own beautiful graphics. (You wonderful graphics folks can send in a fixed-up FLAs with better graphics in the Library, and we’ll gladly add them on the front of this post!). Click the download button to get the whole enchilada:

kilroy

This is my last post for 2009. See you all next year, and have a great holiday!

0 Response to “ActionScript 3.0 Design Pattern Re-use and Modification”


  • No Comments

Leave a Reply