Friends with Benefits: State and Factory Method Together at Last—Part II

In the first part of this article, Friends with Benefits: Refactoring with Multiple Design Patterns—Part I you saw how a Factory Method design pattern was used to hold and deliver data to a DataProvider object. The Factory Method design pattern allowed for easy update by simply adding items to concrete products from a Product interface (DataSupply) or new concrete Product classes. New data can be added to the Concrete Products and the Client makes requests through the Creator interface. The request is filtered through the Concrete Creator to the Product interface to the concrete Product classes. Now, all we need to do is to refactor the player with a design pattern. However, before starting download the application (With 12 classes in the design patterns, a Client class plus three folders of videos, there’s a lot!) So first off, click here to Download All Files . You can see a working version of the player and multiple data sets here. These will help pull together all of the elements used in this project.

Here Comes the Big Bad State Machine

Before we get going on refactoring the video player application, we need to see where it will go in the context of both the State and the Factory Method design patterns. Figure 1 shows the relationship between the refactored player (State) and the factory that delivers the data (Factory Method).
dualdpfilessm
Figure 1: Files for two design patterns in class diagram

The images are organized in an “open” folder, and the videos are placed in separate folders showing their relative position to the classes making up the two design patterns. The Client makes a request through the Factory Method to populate a List component with data from a DataProvider. Then, the request from the Client to the player can be made through the List component by clicking on the video selection. Figure 2 shows what the final product looks like so that you can more easily follow the refactoring:

dualvid500

Figure 2: Selected video running in state machine player

If Figure 2 looks different from what we started in Part I of this article, it’s because the application changed. Originally, the plan was to take the application made for showing F4V files (H.264 formatted HD files) using the latest version of Flash Media Server (FMS) and show how it was refactored. However, since most readers do not develop with FMS, the plan was changed to show a progressive download application that anyone could use. The State design pattern is almost identical to the one used in our book to develop a player. Likewise, we don’t plan to try your patience by rehashing Chapter 10 or the article on DevNet that cover statecharts, the details of state machines and the State Design Pattern. Instead, I’d like to focus on the Client and show how it makes requests to different sets of classes using different design patterns. Therefore, I’ll only briefly go over the State Design Pattern to help you orient yourself to its use in the current context with another design pattern.

State Machine as Time Machine

I like to imagine the state machine as a time machine. Each time represents a different state. As you go from one state to another, things look different and the possibilities are different. If you go to the mid 18th century you’ll find horse-powered transportation and in that state you’ll find no air transportation beyond hot air balloons. Conversely, if you go into the future to the 22nd century you may find transportation that runs only on hydrogen and air travel only occurs beyond the stratosphere. In different time periods you expect different capabilities. Likewise, in different states, you have different expectations. For example, in the video player, if you are in a stop state you cannot stop. That’s because in that state, you’re already stopped. Likewise, in a play state you cannot go to play, because you’re already there.

Some states are trickier than others, but you have options. In one video player I made, I would not allow the user to go from Pause to Play or Stop. The user had to first Un-Pause by going to Pause again. That was when the main Pause state was set up as a toggle. One blogger complained that he just couldn’t deal with a Player that would not allow one to go from Pause to Stop or Play. The reason behind my decision was that some funny things happened when one left the Pause state without un-pausing it first. (Now that ActionScript 3.0 has a NetStream.resume() method, the toggle is no longer required.) Of course, if one decides that it’s perfectly fine to go from a Pause state to a Play or Stop; all you’ve got to do is to change the code in the Pause state—in fact it’s really easy to do and is one of the advantages of the State Design Pattern. (In case you haven’t noticed, I have no god-like powers to enforce code decisions—you’re programmers; so change the code all you want!)

By setting up your program as state machines, you have the option of easily changing the states. You can change the contents of the states (as suggested in the previous paragraph) or you can add new states. As your application changes and grows, all you need to do is to change the states to reflect the application’s functionality.

Avoiding Conditional Statements

My favorite feature of the State Design Pattern is that it has no conditional statements. Count ‘em—none. In fact, in this entire program, you’ll find only a single switch statement in the ConcreteCreatorVid class in the Factory Method pattern portion of the program. If you look at different Factory Method patterns in most languages, you’ll find that the concrete creator classes contain one or more conditional statement; so, sometime you do need them. (I’ve concealed one more conditional in the Client that takes care of a little bling, but you’ll have to search to find it—besides it doesn’t count because it’s in the Client.)

What do I have against conditional statements? Well, it’s not so much that I have anything against conditionals as it is the fact I like the idea of not using them. The State design makes you stop and think of what possibly can happen in a given state. Then instead of first having to filter through conditional statements, you simply do exactly what the current state allows. The logic flow is more pleasing and forces me to be a better programmer.

The Mighty Client

One of the original goals of refactoring my video player was to give it more flexibility and to cut down on the size of a single big class—essentially a huge self-serving Client. The idea of the Client is to make requests, and it should not be used as an everything class. Several design patterns include the Client as a participant, but even they recognize the role of the Client as a request-maker.

In Figure 1, you can see that the Client makes requests through the Creator interface in the Factory Method pattern and through the Context class in the State pattern. A Client making requests from multiple classes or interfaces is not unusual—consider the Abstract Factory pattern. The request-making involves a set of UIs for different data sets for the DataProvider object. It provides the List UI with selections used to request the different videos available for displaying in the player. The following listing shows that the great majority of the Client is taken up by UIs and methods for making request with the UIs.

?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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package 
{
	//Progressive Download
	import fl.controls.Button;
	import fl.controls.List;
	import fl.events.ListEvent;
	import fl.data.DataProvider;
	import flash.display.Sprite;
	import flash.display.Shape;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.events.NetStatusEvent;
	import flash.media.Video;
	import flash.events.MouseEvent;
 
	public class Client extends Sprite
	{
		private var btnGp1:Button=new Button();
		private var btnGp2:Button=new Button();
		private var btnGp3:Button=new Button();
		private var playList:List=new List();
		private var stopBtn:Button;
		private var pauseBtn:Button;
		private var resumeBtn:Button;
		private var nc:NetConnection=new NetConnection();
		private var ns:NetStream;
		private var vid:Video = new Video();
		private var context:Context=new Context();
		private var flv:String;
		private var metaCap:Object;
		private var stopState:Boolean;
		private var mset:Boolean = false;
		private var group1:DataProvider=new DataProvider();
		private var group2:DataProvider=new DataProvider();
		private var group3:DataProvider=new DataProvider();
		private var create:Creator=new ConcreteCreatorVid();
 
		public function Client()
		{
			nc.connect(null);
			ns = new NetStream(nc);
			//Handle end of flv play status
			ns.addEventListener(NetStatusEvent.NET_STATUS,flvCheck);
 
			setUI();
			setProviders();
 
			//Set up object to capture metadata
			metaCap = new Object  ;
			ns.client = metaCap;
			metaCap.onMetaData = getMeta;
		}
 
		private function setUI():void
		{
			addChild(vid);
			vid.x = 150;
			vid.y = 50;
			vid.attachNetStream(ns);
 
			//Video Buttons and List Components 
			playList.x = 20,playList.y = 50;
			addChild(playList);
 
			group1 = create.selectData("group1");
			playList.dataProvider = group1;
 
			stopBtn=new Button();
			stopBtn.label = "Stop";
			stopBtn.width = 50;
			stopBtn.x = 142,stopBtn.y = 352;
			addChild(stopBtn);
 
			pauseBtn=new Button();
			pauseBtn.label = "Pause";
			pauseBtn.width = 50;
			pauseBtn.x = 206,pauseBtn.y = 352;
			addChild(pauseBtn);
 
			resumeBtn=new Button();
			resumeBtn.label = "Resume";
			resumeBtn.width = 55;
			resumeBtn.x = 270,resumeBtn.y = 352;
			addChild(resumeBtn);
 
			//Add Event Listeners
			playList.addEventListener(ListEvent.ITEM_CLICK,doPlay);
			stopBtn.addEventListener(MouseEvent.CLICK,doStop);
			pauseBtn.addEventListener(MouseEvent.CLICK,pauseNow);
			resumeBtn.addEventListener(MouseEvent.CLICK,resumeNow);
		}
 
		private function setProviders():void
		{
			//Data provider buttons
			btnGp1.x = 20,btnGp1.y = 160;
			btnGp1.label = "Group1";
			addChild(btnGp1);
 
			btnGp2.x = 20,btnGp2.y = 190;
			btnGp2.label = "Group2";
			addChild(btnGp2);
 
			btnGp3.x = 20,btnGp3.y = 220;
			btnGp3.label = "Group3";
			addChild(btnGp3);
 
			//Add Event Listeners
			btnGp1.addEventListener(MouseEvent.CLICK,getGp1);
			btnGp2.addEventListener(MouseEvent.CLICK,getGp2);
			btnGp3.addEventListener(MouseEvent.CLICK,getGp3);
		}
 
		private function getMeta(mdata:Object):void
		{
			(mset) ? removeChildAt(0) : null;
			mset = true;
			vid.width = mdata.width;
			vid.height = mdata.height;
			var backdrop:Shape=new Shape();
			backdrop.graphics.beginFill(0x611427,1);
			backdrop.graphics.drawRect(vid.x,vid.y,vid.width,vid.height);
			addChildAt(backdrop,0);
		}
 
		private function getGp1(e:MouseEvent):void
		{
			group1 = create.selectData("group1");
			playList.dataProvider = group1;
		}
 
		private function getGp2(e:MouseEvent):void
		{
			group2 = create.selectData("group2");
			playList.dataProvider = group2;
		}
 
		private function getGp3(e:MouseEvent):void
		{
			group3 = create.selectData("group3");
			playList.dataProvider = group3;
		}
 
		private function flvCheck(e:NetStatusEvent):void
		{
			stopState=(e.info.code=="NetStream.Play.Stop");
			(stopState) ? halt():null;
		}
 
		private function halt():void
		{
			context.stopPlay(ns);
			vid.clear();
		}
 
		private function doPlay(e:ListEvent):void
		{
			flv = e.item.data;
			context.startPlay(ns,flv);
		}
 
		private function doStop(e:MouseEvent):void
		{
			halt();
		}
 
		function pauseNow(e:MouseEvent):void
		{
			context.pausePlay(ns);
		}
 
		function resumeNow(e:MouseEvent):void
		{
			context.resumePlay(ns);
		}
	}
}

I’ve got to admit that for a Client class, this one seems pretty big. However, all of the data for selecting the videos and working the video player are either part of the Factory Method or State design patterns. With only slight modifications in the Client this same player can handle FMS files, including F4V files. I’ve used this same State pattern for several different kinds of players, including the one operating FMIS 3.2 and playing high definition videos ( such as these) that we discussed in Part I of this post.

The Context Class

The Context class is the key to understanding the State Design Pattern as a pattern and not simply a state machine. When you look at it, the class appears to be nothing more than a getter/setter class. However, it is far more. It establishes which state is the current state. By setting up the current state, it provides for one of the best examples of polymorphism that can be found in OOP. The State interface provides four methods, but depending on the current concrete state class, each method is implemented differently. The following listing shows the Context class used in this example:

?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
package 
{
	//Context Class
	import flash.net.NetStream;
 
	class Context
	{
		private var playState:State;
		private var stopState:State;
		private var pauseState:State;
		private var resumeState:State;
		private var state:State;
		public function Context()
		{
			trace("Video Player is on");
			playState = new Play(this);
			stopState = new Stop(this);
			pauseState = new Pause(this);
			resumeState = new Resume(this);
			state=stopState;
		}
		public function startPlay(ns:NetStream,flv:String):void
		{
			state.startPlay(ns,flv);
		}
		public function stopPlay(ns:NetStream):void
		{
			state.stopPlay(ns);
		}
		public function pausePlay(ns:NetStream):void
		{
			state.pausePlay(ns);
		}
		public function resumePlay(ns:NetStream):void
		{
			state.resumePlay(ns);
		}
 
		public function setState(state:State):void
		{
			trace("A new state is set");
			this.state=state;
		}
		public function getState():State
		{
			return state;
		}
		public function getPlayState():State
		{
			return this.playState;
		}
		public function getStopState():State
		{
			return this.stopState;
		}
		public function getPauseState():State
		{
			return this.pauseState;
		}
		public function getResumeState():State
		{
			return this.resumeState;
		}
	}
}

The Context class delegates requests (from the Client) to a State object. All methods either use or return a State object. The trick in the Context is to keep track of the current state so that when a requested method is returned to the Client, it is done in the appropriate manner. (e.g., a stop play request will not be sent from a stop state.)

The State Interface and the States

The state interface and the concrete states are relatively easy. Using either an interface or abstract class, the State interface provides the essential methods that can be implemented in the concrete state classes. First, the State interface shows four simple methods—playing, stopping, pausing and resuming video play:

?View Code ACTIONSCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
package
{
	//State Interface
	import flash.net.NetStream;
	interface State
	{
		function startPlay(ns:NetStream,flv:String):void;
		function stopPlay(ns:NetStream):void;
		function pausePlay(ns:NetStream):void;
		function resumePlay(ns:NetStream):void;
	}
}

All of the methods have the same parameters except for the startPlay() method that has an additional string parameter for the video URL. The rest are for directing actions in the NetStream() object.

Finally, the concrete State classes that implement the State interface are set states. That means that the action associated with the state has been set so that you can possibly do whatever that state allows. For example, if you’re in the Stop state, you can go to the Play state, but since you’re already in the Stop state, it doesn’t make any sense to go there. (Think of yourself at a stop sign in traffic. You can go to “drive” but since you’re at stop, you cannot “go” there.) So, without further ado, let’s look at the four state classes:

?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
144
145
146
147
package 
{
	//Stop State
	import flash.net.NetStream;
 
	class Stop implements State
	{
		private var context:Context;
		public function Stop(context:Context)
		{
			trace("--Stop State--");
			this.context=context;
		}
		public function startPlay(ns:NetStream,flv:String):void
		{
			ns.play(flv);
			trace("Begin playing");
			context.setState(context.getPlayState());
		}
		public function stopPlay(ns:NetStream):void
		{
			trace("You're already stopped");
		}
		public function pausePlay(ns:NetStream):void
		{
			trace("Cannot go to Pause from Stop.");
		}
		public function resumePlay(ns:NetStream):void
		{
			trace("Cannot go to Resume from Stop.");
		}
	}
}
 
 
package 
{
	//Play State
	import flash.net.NetStream;
 
	class Play implements State
	{
		private var context:Context;
		public function Play(context:Context)
		{
			trace("--Play State--");
			this.context=context;
		}
		public function startPlay(ns:NetStream,flv:String):void
		{
			trace("You're already playing");
		}
		public function stopPlay(ns:NetStream):void
		{
			ns.close();
			trace("Stop playing.");
			context.setState(context.getStopState());
		}
		public function pausePlay(ns:NetStream):void
		{
			ns.pause();
			trace("Begin pause.");
			context.setState(context.getPauseState());
		}
		public function resumePlay(ns:NetStream):void
		{
			trace("Cannot resume from a play state.");
		}
	}
}
 
 
package 
{
	//Pause State
	import flash.net.NetStream;
 
	class Pause implements State
	{
		var context:Context;
		public function Pause(context:Context)
		{
			trace("--Pause State--");
			this.context=context;
		}
		public function startPlay(ns:NetStream,flv:String):void
		{
			trace("You have to go to resume");
		}
		public function stopPlay(ns:NetStream):void
		{
			trace("Don't go to Stop from Pause");
			//Allow going from Pause state to Stop state
			//ns.close();
			//context.setState(context.getStopState());
		}
		public function pausePlay(ns:NetStream):void
		{
			trace("Cannot pause from Pause state.");
		}
 
		public function resumePlay(ns:NetStream):void
		{
			ns.resume();
			trace("Quit pausing.");
			context.setState(context.getResumeState());
		}		
	}
}
 
 
package 
{
	//Resume
	import flash.net.NetStream;
 
	class Resume implements State
	{
		var context:Context;
		public function Resume(context:Context)
		{
			trace("--Resume State--");
			this.context=context;
		}
		public function startPlay(ns:NetStream,flv:String):void
		{
			trace("Start over");
			context.setState(context.getPlayState());
		}
		public function stopPlay(ns:NetStream):void
		{
			ns.close();
			trace("Stop playing.");
			context.setState(context.getStopState());
		}
		public function pausePlay(ns:NetStream):void
		{
			ns.pause();
			trace("Pause.");
			context.setState(context.getPauseState());
		}
		public function resumePlay(ns:NetStream):void
		{
			trace("Already in resume state.");
		}
	}
}

In addition to providing a set of methods that direct the program to the next possible set of states, the constructor function in each state class sets itself as the current state. Put differently, each state sets the current context to itself.

Using Multiple Design Patterns

Creating programs with multiple design patterns is no more unusual than writing programs with multiple methods or properties. Further, this particular application suggests that you can mix and match design patterns and reuse the major components for any number of projects. Further, the Client doesn’t know (or care) what changes are made in the different components as long as they follow the rules laid out by the interface and the request to the appropriate component in the pattern is unchanged.

The difficult part is doing it right the first time. Then, later as the program requires changes (as it always does), it’s easy to do so. Further, you can take the exact same design patterns and use them for different applications, which saves time as well.

As a final note, I’ve left several trace statements in the code to help you better see what happens in different states. Once in a given state and running the program from within either Flex or Flash, note the trace output. Also, try requesting “impossible” states—such as pressing the Stop button when the program is already stopped. Finally, if you do not like a condition placed on a state (such as disallowing movement from one state to a certain different state), go ahead and change the program.

  • Share/Bookmark

Related posts:

  1. Friends with Benefits: Refactoring with Multiple Design Patterns—Part I
  2. MVC and Factory Method Pattern Chapters on Adobe DEVNET
  3. Flexibility Pays Off with Template Method

6 Responses to “Friends with Benefits: State and Factory Method Together at Last—Part II”


  • Hi,

    thanks for all your well written articles! Regarding the state pattern: The video player is a common, simple and useful example to demonstrate the pattern, however, it would be interesting to see an implementation of a more “tricky” example. For example, imagine a web application that has different view states and transitions (animations) from one state to the next. One would have to deal with asynchronous events (transitioning to the next state as soon as the animation is complete). In addition, mouse clicks from users must be blocked during the transition to prevent unwanted side effects with the animation. Overall, much more coordination would be necessary. Would there be separate “animationIn” or “animationOut” states etc., where and how are the events handled? I think this is a common scenario for a lot of flash sites, web apps and would make for a useful “real-world” example (video players are, of course, also very “real-world” but maybe a bit overused for the state pattern ;)

  • Hi Mystery Person,

    I’ve been working on another State Design Pattern for a magazine article I’m doing, and I agree that greater variety of State dps (from me at least) would be a good thing.(The new one I’m working on has nothing to do with video, and Chandima pitched in and got the project back on track after I got stuck in the mud). It turns out that some tricky operations are involved when you get away from system level operations that can be found in NetStream objects.

    Changing view states sounds like a great challenge. How about this?——hack together an example of what you have in mind, and I’ll work it into a State dp. In that way I can see exactly what you have in mind and you can see how to approach refactoring with a common ‘real world’ use that our readers can gain value from as well.

    Kindest regards,
    Bill

  • Sounds good. I am also interested in this….

  • Hi again Mark,

    Why not join Mystery Person and come with a non-dp example of changing view states and we can use it for refactoring.

    Thanks,
    Bill

  • hi Bill,
    thanks to all your documentation on design patterns, I’m now trying to redesign my own flash-game project into something more fancy. It costed me some sweat ,but I’m getting to the point where I have my very own state machine running. And I’m planning to combine it with a MVC.
    I have one question though; why do you use several public functions like getPlayState():State ? Couldn’t you as well change “private var playState:State;”
    into
    “public var playState:State;” ?
    Is there any reason for this? It seems to me it’s just more writing..

    thanks

  • Hi Paul,

    State machines are just plain cool. They get right to the point without using conditionals.

    The reason for the public access is that the methods are called by another object. Generally, when you have a return value, you can expect an external call(not if the call is made within the same class, of course).

Leave a Reply