Take a Design Pattern to Work Part IV: Establishing a Design Pattern Foundation

Gentle Reader: This is Part 4 of a four-part series of posts on introducing design patterns and OOP into the work place. Parts 1 through 3 will provide the context for this part. Also, taking a look at No Time for OOP and Design Patterns will give you the background on this series. As always, we invite your comments.

Note: Chandima wrote the chapter in our book on the Factory Method, and he gave me invaluable help on the main program in this post as well.

Recap

Up to this point we’ve examined a simple program that loads external text and graphics, a common ActionScript chore. In the most general terms, this is where we’ve been:

  • Part I: Identifying the problem in a current solution. Why ActionScript on the Timeline can cause problems.
  • Part II: Providing a simple OOP solution: Use of Inheritance
  • Part III: Loosening Up a programs structure: Adding a design pattern element —a simple factory

To conclude the process, we now come to the last part—introducing an actual design pattern to the work place.

  • Part IV: Establishing a Design Pattern Foundation.

Given the preceding steps, the context is now in place to add a full design pattern.

From Part to Whole

Part III introduced the Simple Factory method inserted into an existing OOP program. Now it’s time to step back and look at a design pattern en toto and instead of incrementally adding to the existing program, we will refactor the whole kit-n-kaboodle from the perspective of a design pattern.

To get started, if you’re not familiar with the Factory Method pattern, take a look at Chapter 2. In fact Chandima’s Sprite Factory example beginning on page 84 is one of the clearest and most appropriate examples that you can find of the Factory Method pattern in ActionScript 3.0. So before continuing, you might want to do a quick review of the Factory Method and take a look at Figure 1, the class diagram for the pattern. (We’ll wait for you…).

factorymethoddp852

Figure 1: Factory Method Design Pattern

As you can see, the Factory Method (simple factory) is part of the Creator interface and the ConcreteCreator. The interface is an abstract class; so at least one of the methods needs to be abstract—impossible to directly instantiate but easily overridden in a child class.

The Product class is an abstract version of what we called the Staff class in the previous version of the application. Again, using an abstract class requires abstract methods to be overridden in the child classes. The ConcreteProduct classes were previously the Designer, Developer and HCI classes. We can keep the old designations for clarity, but their parent class is now the Product interface (abstract class). That means that the child classes can now make adjustments to the methods using the override statement. However, those methods not overridden do not need to be revised at all. Figure 2 shows the design pattern (without the pseudo-code blocks.) and with the actual child classes for the Product abstract class.
factorymethodstaff

Figure 2: Factory Method Pattern applied to current project

As a reminder, the dashed lines with the arrows means that origin of the dashed line creates an instance of the class to which the arrowhead points.

One Day They Came to Work and Found All These Design Patterns

In the process of creating this series of posts and working on a presentation for the Atlanta group with similar aims, I’ve been able to review the process of introducing design patterns at work. My conclusion is that we need to step both gently and firmly, and by all means do so in a practical way. (When the choice is between being on time and being right, take on time.) However, OOP and design patterns need to be at the back of your mind so that you can begin viewing projects in terms of OOP and design patterns. In turn, this will make your company’s products better, more flexible and less likely to choke up at the wrong time. (They never choke up at the right time—whenever that is.)

So, in looking at this refactored program, keep in mind that we started with a simple program embedded in the Timeline using keyframes to choose graphic and text images. Also, keep in mind the problem, and how the design pattern became a solution for that problem.

The Client Looks the Same

We’ll start with the Client, and not surprisingly, it is little changed from last time. However, the request is typed to an abstract class this time (factory:Creator).

?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
package
{
	import flash.display.Sprite;
	import fl.controls.Button;
	import flash.events.MouseEvent;
 
	//Client Class
 
	public class Client extends Sprite
	{
		private var logo:Sprite = new Logo();
		private var splash:Sprite = new Splash();
		private var desBtn:Button = new Button();
		private var devBtn:Button = new Button();
		private var hciBtn:Button = new Button();
		private var staffNow:String;
		private var staffDisplay:Sprite;
		private var staff:String;
		private var factory:Creator=new ConcreteCreator();
 
		public function Client()
		{
			desBtn.label = "Designer";
			desBtn.addEventListener(MouseEvent.CLICK,selectStaff);
			addChild(desBtn);
			desBtn.x = 424,desBtn.y = 66;
 
			devBtn.label = "Developer";
			devBtn.addEventListener(MouseEvent.CLICK,selectStaff);
			addChild(devBtn);
			devBtn.x = 424,devBtn.y = 116;
 
			hciBtn.label = "HCI";
			hciBtn.addEventListener(MouseEvent.CLICK,selectStaff);
			addChild(hciBtn);
			hciBtn.x = 424,hciBtn.y = 166;
 
			logo.x = 16,logo.y = 16;
			logo.width = 150,logo.height = 121.5;
			addChild(logo);
 
			splash.x = 32,splash.y = 168;
			addChild(splash);
		}
 
		private function selectStaff(e:MouseEvent)
		{
			staff = e.target.label.toLowerCase();
			splash.visible=false;
 
			if (staffDisplay)
			{
				removeChild( staffDisplay );
			}
 
			staffDisplay = factory.makeStaff(staff);
			addChild( staffDisplay );
		}
	}
}

This next class, the Creator, is an abstract one. The functionality of this class is not appreciated in this particular application because we derive only a single child from it. However, if we wanted to change the program to create other elements that we may wish, such as a button factory we could do so relatively easily, and because of the loose linkage, adding another child will not disrupt the overall program.

?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
package
{
	//Asbstract class
	//Creator
	import flash.display.Sprite;
	import flash.errors.IllegalOperationError;
 
	public class Creator
	{
		private var staffNow:Sprite;
 
		public function makeStaff(staff:String):Sprite
		{
			staffNow = createStaff(staff);
			return staffNow;
		}
 
		// ABSTRACT Method (must be overridden in a subclass)
		protected function createStaff(staff:String):Sprite
		{
			throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
			return null;
		}
	}
}

The Creator provides the abstract method for the factory in the Factory Method design pattern. The ConcreteCreator is where the actual factory work is accomplished.

?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
package
{
	//Factory
	import flash.display.Sprite;
	import flash.errors.IllegalOperationError;
 
	public class ConcreteCreator extends Creator
	{
		override protected function createStaff(staff:String):Sprite
		{
			switch (staff)
			{
				case "designer" :
					return ( new Designer() );
					break;
				case "developer" :
					return ( new Developer() );
					break;
				case "hci" :
					return ( new HCI() );
					break;
				default :
					throw new Error("Invalid type of staff specified");
					return null;
			}
		}
	}
}

As you can see, that’s almost identical to the Factory class in Part III of this series of posts. The createStaff() method is overridden. Because it is a child of the Creator class, it has the makeStaff() method, and it uses the creatStaff() method in any way that it has been overridden in the concrete creator itself.

The Product and Its Children

A second abstract class, Product, as noted above, is very similar to the Staff class introduced in Part II of this series. The difference is that the Staff class was concrete and the Product class is abstract. Two of its methods are abstract, and both need to be overridden and detailed in the child classes. So instead of inheritance to a concrete class with no flexibility, the child classes of the Product can customize the overridden functions. The following shows how the Product class is set up:

?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
package
{
	import flash.errors.IllegalOperationError;
	import flash.net.URLLoader;
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldType;
	import flash.events.Event;
	import flash.text.TextFormat;
 
	//Abstract class
	public class Product extends Sprite
	{
		private var loadCheck:URLLoader;
		private var txtFld:TextField = new TextField();
		private var textFormat:TextFormat = new TextFormat();
		private const WIDE:int=200;
		private const TEXTCOLOR:uint=0x007700;
 
		protected function loadInfo(txtInfo:String):void
		{
			throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
		}
 
		protected function nowLoaded(e:Event):void
		{
			loadCheck = URLLoader(e.target);
			textFormat.font = "Arial Black";
			textFormat.size = 16;
			txtFld.type = TextFieldType.DYNAMIC;
			txtFld.width = WIDE;
			txtFld.wordWrap = true;
			txtFld.textColor =TEXTCOLOR;
			txtFld.defaultTextFormat = textFormat;
			txtFld.text = loadCheck.data;
			addChild(txtFld);
			txtFld.x = 180,txtFld.y = 331;
		}
 
		protected function loadImg(img:String):void
		{
			throw new IllegalOperationError("Abstract method: must be overridden in a subclass");
 
		}
	}
}

The Product has two abstract and one concrete methods. The thinking is that the onLoaded() operation is not going to change, but is needed to have everything laid out for the incoming Sprite object containing the image and text. However, we might come up with positioning ideas for the image that we can change. As for the abstract method for adding the text, it’s probably not going to change, but if it does, we can make the changes in the child classes. If we want to re-position the text, we can still use the concrete method in the Product class itself to change all text.

The result of having abstract methods is that they need to be filled out (populated by code) in the child classes. As a consequence, the concrete Product classes (Designer, Developer, and HCI) are all larger than the tiny ones in previous posts in this series. They have to override the abstract methods and add the code they want. In this example, they’re virtually identical to one another, but if we wanted to change one, we could do so without interfering with the other concrete child Product classes. The following three classes show how the concrete products are created:

?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
142
143
//Designer
package
{
	import flash.net.URLRequest;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	import flash.display.Sprite;
	import flash.events.Event;
 
	public class Designer extends Product
	{
		private var staffText:String;
		private var staffImg:String;
		private var url:URLRequest
		private var loader:Loader
		private var fileNow:URLRequest;
		private var txtLoader:URLLoader;
		private var loadCheck:URLLoader;
 
		public function Designer()
		{
			staffText = "text/designer.txt";
			staffImg = "images/designer.png";
 
			loadInfo(staffText);
			loadImg(staffImg);
		}
 
		override protected function loadInfo(txtInfo:String):void
		{
			txtLoader = new URLLoader();
			txtLoader.addEventListener(Event.COMPLETE,nowLoaded);
			fileNow = new URLRequest(txtInfo);
			txtLoader.load(fileNow);
		}
 
		override protected function loadImg(staffImg:String):void
		{
			url = new URLRequest(staffImg);
			loader=new Loader();
			loader.load(url);
			addChild(loader);
			loader.x = 180,loader.y = 131;
		}
	}
}
 
//Developer
package
{
	import flash.net.URLRequest;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	import flash.display.Sprite;
	import flash.events.Event;
 
	public class Developer extends Product
	{
		private var staffText:String;
		private var staffImg:String;
		private var url:URLRequest
		private var loader:Loader
		private var fileNow:URLRequest;
		private var txtLoader:URLLoader;
		private var loadCheck:URLLoader;
 
		public function Developer()
		{
			staffText = "text/developer.txt";
			staffImg = "images/developer.png";
 
			loadInfo(staffText);
			loadImg(staffImg);
		}
 
		override protected function loadInfo(txtInfo:String):void
		{
			txtLoader = new URLLoader();
			txtLoader.addEventListener(Event.COMPLETE,nowLoaded);
			fileNow = new URLRequest(txtInfo);
			txtLoader.load(fileNow);
		}
 
		override protected function loadImg(staffImg:String):void
		{
			url = new URLRequest(staffImg);
			loader=new Loader();
			loader.load(url);
			addChild(loader);
			loader.x = 180,loader.y = 131;
		}
	}
}
 
//HCI
package
{
	import flash.net.URLRequest;
	import flash.display.Loader;
	import flash.net.URLRequest;
	import flash.net.URLLoader;
	import flash.display.Sprite;
	import flash.events.Event;
 
	public class HCI extends Product
	{
		private var staffText:String;
		private var staffImg:String;
		private var url:URLRequest
		private var loader:Loader
		private var fileNow:URLRequest;
		private var txtLoader:URLLoader;
		private var loadCheck:URLLoader;
 
		public function HCI()
		{
			staffText = "text/hci.txt";
			staffImg = "images/hci.png";
 
			loadInfo(staffText);
			loadImg(staffImg);
		}
 
		override protected function loadInfo(txtInfo:String):void
		{
			txtLoader = new URLLoader();
			txtLoader.addEventListener(Event.COMPLETE,nowLoaded);
			fileNow = new URLRequest(txtInfo);
			txtLoader.load(fileNow);
		}
 
		override protected function loadImg(staffImg:String):void
		{
			url = new URLRequest(staffImg);
			loader=new Loader();
			loader.load(url);
			addChild(loader);
			loader.x = 180,loader.y = 131;
		}
	}
}

This ends the classes that make up the Factory Method pattern used in this post. They’re available to download. Just click the Download button below and load ‘em up in either Flash Builder or Flash.

kilroy

Back to Work

As this New Year begins with new problems and opportunities for solving problems, we really need to think about the quality of our work. Down the line somewhere, a program that you helped write with good OOP and design pattern practices may solve someone else’s problem. The program may have one feature that because of what you did made it better. Maybe if we just do a little better for ourselves in terms of program practices, something good will come of it. Then at this time next year, we’ll all be a little better. For me, that’s enough.

2 Responses to “Take a Design Pattern to Work Part IV: Establishing a Design Pattern Foundation”


  • Hi Bill,

    I am not someone who usually replies to a article that I have read, but I had to reply to these series of articles. Your final solution is so elegant and straight forward that you just can’t help but use it and adapt it. Your book and this website is pure gold and is a major contributor to improving the standards of Actionscript programming. Please keep up the good work.

    David Wright

  • Hi David,

    Well that certainly gave us a good reason to keep on keeping on.

    Kindest regards,
    Bill

Leave a Reply