The State pattern in Flex - combining view states with logical states
By Mims Wright | on January 23, 2008
In AS3, Flex, Hacks, Programming, Software Design, Tutorial, User Interface Design |
If you’ve used Flex, you’ve no doubt (er, hopefully) been using View States (AKA <mx:State>) to change the look of your RIA as it progresses through different situations of use. While this is immeasurably useful, it does not necessarily qualify as an implementation of the State Design Pattern which allows you to change not only how a component looks but how it functions as well.
(for more on design patterns, read my favorite book Head first design patterns).
Take the following example for a “Breakfast maker” application.
StateDemo.mxml
[ftf]
// define the state names as constants
static public const ENGLISH:String = "English";
static public const FRENCH:String = "French";
/**
* When the "make breakfast" button is clicked, display the steps
* you'll need to follow to make breakfast.
* Imagine that you'd need to implement all of these steps as functions
* and that there are several more languages to support.
*/
protected function onBreakfastButton(event:Event):void {
if (currentState == ENGLISH) {
output.text = "Fry eggs" +
"\nFry tomatoes" +
"\nToast bread" +
"\nMake tea";
}
if (currentState == FRENCH) {
output.text = "Tartiner du beurre sur baguette" +
"\nMettre le yaourt dans un bol" +
"\nTranche les fruits" +
"\nFaire cafe";
}
}
]]>
[/ftf]
Compiling this code gives you the following…
The problem
While there’s nothing inherently wrong with this application, there could be complications when trying to make changes to the logic. First of all, the onButtonClick() handler has to provide different functionality based on the currentState. The states themselves need to add labels to the breakfastButton. Imagine what would happen if you expanded this application to support 15 languages or to create lunch, dinner, and teatime. Things could get out of control fast!
The Solution - applying the state design pattern
The state design pattern allows you to separate out state specific functionality and defer the execution of methods to those state objects.

I’ll combine the state pattern with the view states by making a currentLogicalState property of the application. This setter will also automatically set the currentState based on a name stored in the currentLogicalState. This is easier done than said…
1. Define a logical state interface
My logical state interface only needs to have one getter for name so that we can associate it with a view state.
ILogicalState.as
[ftf]
package
{
public interface ILogicalState
{
function get name():String;
}
}
[/ftf]
2. create a customized state interface for this application
This interface should contain any methods that are state dependent.
IBreakfastMakerState.as
[ftf]
package
{
public interface IBreakfastMakerState extends ILogicalState
{
function get breakfastInstructions():String;
function get breakfastLabel():String;
}
}
[/ftf]
Note that this interface extends ILogicalState.
3. create state classes
I’ll create two different state classes, one for English breakfast and one for French Breakfast. Each will implement the IBreakfastMakerState interface and will provide the functionality needed to prepare the appropriate breakfast.
EnglishBreakfastState.as
[ftf]
package
{
public class EnglishBreakfastState implements IBreakfastMakerState
{
public function get name():String
{
return “English”;
}
public function get breakfastInstructions():String
{
return “Fry eggs” +
“\nToast bread” +
“\nFry tomatoes” +
“\nMake tea”;
}
public function get breakfastLabel():String {
return “Make Breakfast”;
}
}
}
[/ftf]
FrenchBreakfastState.as
[ftf]
package
{
public class FrenchBreakfastState implements IBreakfastMakerState
{
public function get name():String
{
return “French”;
}
public function get breakfastInstructions():String
{
return “Tartiner du beurre sur baguette” +
“\nMettre le yaourt dans un bol” +
“\nTranche les fruits” +
“\nFaire cafe”;
}
public function get breakfastLabel():String {
return “Préparer des petits-déjeuners”;
}
}
}
[/ftf]
4. Refactor StateDemo.mxml
Finally, rewrite the StateDemo.mxml to take advantage of the new state objects and call it LogicalStateDemo.mxml. (Note, this application should run exactly the same as StateDemo.mxml)
// define the logical states as constants /** protected function onInitialize(event:Event):void { protected function onBreakfastButton(event:Event):void {LogicalStateDemo.mxml
[ftf]
static public const ENGLISH:IBreakfastMakerState = new EnglishBreakfastState();
static public const FRENCH:IBreakfastMakerState = new FrenchBreakfastState();
* keep track of the current logical state and when the currentLogicalState changes
* update the currentState using its name property.
*/
protected var _currentLogicalState:IBreakfastMakerState;
public function set currentLogicalState(state:IBreakfastMakerState):void {
_currentLogicalState = state;
currentState = _currentLogicalState.name;
}
currentLogicalState = ENGLISH;
}
// rely on the logical state to handle the logic of preparing the breakfast.
output.text = _currentLogicalState.breakfastInstructions;
}
]]>
[/ftf]
Et voila! A state pattern that works with the existing view states. This may seem like a lot of work for such a simple thing but it will really pay off in the long run on a complicated RIA.
2 Comments »
RSS feed for comments on this post. TrackBack URI
Head First Design Patterns is a great book, but I thought I would say that the ActionScript 3.0 Design Patterns: Object Oriented Programming Techniques is based on the same model, but is AS3 specific (NOT the Advanced Actionscript 3 with Design Patterns book, not that great IMO). In fact, I think they used the same outline to write the book. It doesn’t have the pictorific flavor of the Head First series, but you don’t have to make the Java context shift.
Also, your example is broken in my browser. ;)
Comment by Joel — January 23, 2008 #
This is very useful, thanks for posting. Unfortunately the formatting went haywire and I had to resort to reading the HTML source to see the full article. (For both Mac/Safari3 and Mac/FF2).
Comment by Wade — January 24, 2008 #