Pet Theory header image 2

Building Pet 5: The State Pattern for Loading SWF Modules

May 9th, 2007 by matt

In a tree of modules, some modules will need to load assets when selected...and some will themselves need to be loaded.

Creating a class of compiliable modules was primarily a matter of cutting and pasting. I took the BaseModule class:

Actionscript:
  1. //just the constructor
  2. public function BaseModule(aName:String)
  3. {
  4.     _moduleName=aName;
  5.     _moduleParent=null;
  6.     _loadable=null;
  7.     _navigable=null;
  8.     _controllable=null;
  9.     _wakeable=null;
  10.     _alertable=null;
  11. }

...and pasted it into a new CompiledBaseModule class, with this edit:

Actionscript:
  1. //name changed, "extends Sprite" added for compiliability
  2. public class CompiledBaseModule extends Sprite implements IModule

I resisted this crude move at first, fearful that every subclass of BaseModule would similarly have to be pasted over to the CompiledBaseModule line if I wanted a loadable instance of it.

So I latched onto the default plan for all design-pattern impasses: Favor composition over inheritance. Was there a way to internalize loadable spriteness that made it a snap to veer from creating regular modules to creating loadable ones?

There wasn't. A loadable module had to extend Sprite to be compiled, no ifs, ands or buts. And I just knew that in six month's times, I really, really wanted the simplicity of opening up a file, writing "class MyNewMiniApp extends CompiledBaseModule," and compiling away. So I opened up a second line of modules.

I knew that these compiled modules would be loaded by other modules, but I was immediately perplexed by the nature of these other modules:

Actionscript:
  1. //in the parent module
  2. //the child module is only an address at this point
  3. _navigable.addChild(new LoadCompiledModule("MyNewMiniApp.swf"))

Obviously, this LoadCompiledModule is not the thing itself, the compiled module not yet loaded. Presumably, it is a stand-in or proxy replaced when the thing itself arrives.

But replaced when, where, how? The advantage of centralizing and standarizing loadables with an interface was that every loadable could be treated uniformly. If the local module or its navigable has to take special measures to replace a stand-in with the real thing, the elegance and utility of this scheme is spoiled.

Actionscript:
  1. //how many lame hacks like this?
  2. //in a parent module
  3. protected function onLoad(e:Event):void
  4. {
  5.     _navigable.children[5]=_navigable.children[5].loadable.content;
  6. }

So the LoadCompiledModule instance has to remain what it is for the outside world, but also become another module entirely...the same, but different...how is this even possible?

The State Pattern: One Object, Multiple Personalities

The first step to solving this puzzler is surprisingly simple. Start by taking the problem literally. The two different states of the module really ARE two different modules. The first one exists in the parent module, and loads the second one. Let's build it:

Actionscript:
  1. //this module just loads another module
  2. var unloadedState:IModule=new BaseModule("MyMiniApp");
  3. //CompiledDisplayLoadable composes a Loader to load a swf
  4. unloadedState.loadable=new CompiledDisplayLoadable("MyMiniApp.swf", "MyMiniApp");

Now let's build a dummy compiled module:

Actionscript:
  1. class MyMiniApp extends CompiledBaseModule
  2. {
  3.     public function MyMiniApp(aName:String)
  4.     {
  5.         _navigable=new BaseNavigable(this);
  6.         _navigable.addChild(new BaseModule("ONE!", new BaseModule("TWO!"), new BaseModule("THREE!");
  7.         _navigable.setGotos(traceDestinationName, traceDestinationName)
  8.  
  9.     }
  10.     protected function traceDestinationName(index:int)
  11.     {
  12.         trace (_navigable.children[index].moduleName);
  13.     }
  14. }

Once this class is compiled, we can take a second, equally simple step. We have the two modules. We already think of them as two beings inside a single being. So let's make this single, container being, which is the LoadCompiledModule class:

Actionscript:
  1. public class LoadCompiledModule extends ModuleWrapper
  2. {
  3.     //unloaded and loaded states...
  4.     protected var unloadedState:IModule;
  5.     protected var loadedState:IModule;
  6.     protected var _content:DisplayObject;
  7.     //
  8.     public function LoadCompiledModule(url:String, aName:String)
  9.     {
  10.         _content=null;
  11.         //make unloaded state
  12.         unloadedState=new BaseModule(aName);
  13.         unloadedState.loadable=new CompiledDisplayLoadable(url);
  14.         //set decorator's inner module to the unloaded state
  15.         super(unloadedState);
  16.         //prepare for load
  17.         unloadedState.loadable.addLoadListener(loadHandler, false, 3000);
  18.     }
  19.        
  20.     public function get content():DisplayObject
  21.     {
  22.          return _content;
  23.     }
  24.     protected function loadHandler(e:Event):void
  25.     {
  26.         //give contextual info to the compiled module (loaded state)
  27.         _content=unloadedState.loadable.content;
  28.         //this module now wraps a compiled module...
  29.         loadedState=_content as IModule;
  30.         loadedState.moduleName=unloadedState.moduleName;
  31.         loadedState.moduleParent=unloadedState.moduleParent;
  32.         loadedState.siblingIndex=unloadedState.siblingIndex;
  33.         innerModule=loadedState;
  34.         //clean up
  35.         unLoadedState.loadable.removeLoadListener(loadHandler);
  36.     }
  37. }

The key variable in this class is innerModule. At first, this variable points to a module created to load another module (unloadedState). When that second module loads, it points towards it, loadedState, as if to say, YOU are the module I now want to be.

So what use is this now-I'm-this, now-I'm-that container, when what's wanted is a personality-shifting module?

Here is where the fun begins, because LoadCompiledDisplay IS actually a module. Check out its superclass, ModuleWrapper:

Actionscript:
  1. public class ModuleWrapper implements IModule
  2. {
  3.     protected var _innerModule:IModule;
  4.     //
  5.     public function ModuleWrapper(aModule:IModule)
  6.     {
  7.         _innerModule=aModule;
  8.     }
  9.     public function set innerModule(aModule:IModule):void
  10.     {
  11.         _innerModule=aModule;
  12.     }
  13.     public function set moduleName(aName:String):void
  14.     {
  15.         _innerModule.moduleName=aName;
  16.     }
  17.     public function get moduleName():String
  18.     {
  19.         return _innerModule.moduleName;
  20.     }
  21.     public function set navigable(aNavigable:INavigable):void
  22.     {
  23.         _innerModule.navigable=aNavigable;
  24.     }
  25.     public function get navigable():INavigable
  26.     {
  27.         return _innerModule.navigable;
  28.     }
  29.     //and so on...
  30.     //wrap all module getter/setters
  31. }

Do you see what's coming? Basically, this class implements all the methods of IModule, but instead of defining its own methods, it relays all method calls to a inner module.

The innerModule variable is usually named "state," and when the state variable stops pointing at one module (unloadedState) and points at an entirely different module (loadedState), the instance of LoadCompiledModule appears to change its personality because all calls on it are re-routed to the new module:

Actionscript:
  1. //in the parent module, BEFORE a LoadCompiledModule is loaded
  2. //returns a CompiledDislayLoadable for loading swfs
  3. trace (aLoadCompiledModule.loadable)
  4. //AFTER a LoadCompiledModule is loaded
  5. //returns whatever the swf module's loadable is: null, VideoLoadable, etc
  6. trace (aLoadCompiledModule.loadable)

The first loadable call is relayed to the unloadedState module, the second to the compiled module.

Slick, no?

The State pattern always involves one object that relays calls, and any number of similar-seeming objects (unloadedState, loadedState, canceledState, reloadedState, etc.) that it can relay them to.

State among the Wrappers

Since relaying a functional call or "wrapping" is so central to design patterns, let's take a quick tour of the wrappers.

Strategy. In this pattern, an object consolidates some required functionality in a separate, internal object, then relays function calls to it. It "wraps" itself around this object as if the object were an internal organ.

strategy.jpg

This kind of wrapper typically exhibits this sytax:

Actionscript:
  1. //in a IModule-implementing class
  2. public function load()
  3. {
  4.     _loadable.load();
  5. }

Adapter. An adapter takes a pre-existing object and "wraps" it to make it appear and act like something else.

An example might be a ModuleAdapter that takes an Alert instance and makes it act like a module. Such an adapter might become necessary if comments were made inside the alert's pop-up window, and the user decided to incorporate these comments into the children modules (right after the currently selected module, let's say).

Remember that an Alert is a weakish module, with a loadable and a wakeable, but not an alertable, navigable or controllable. So how will a comment-component Alert inserted into the middle of a series of regular modules (slides, text panels, videos, etc) successfully pretend to be a module, without _loadable, _wakeable or _controllable properties?

Like this:

Actionscript:
  1. //this adapter must have all regular module methods
  2. public class AlertToModuleAdapter implements IModule
  3. {
  4.     public function AlertToModuleAdapter(anAlert:Alert)
  5.     {
  6.         innerAlert=anAlert
  7.  
  8.     }
  9.     //alerts have loadables, so pass this on
  10.     public function get loadable():ILoadable
  11.     {
  12.         return innerAlert.loadable;
  13.     }
  14.     //alerts have no alertables, return on null
  15.     //at least now the interrogation of the alert (fakeModule.alertable) does not throw an error
  16.     public function get alertable():IAlertable
  17.     {
  18.         return null;
  19.     }
  20.     ....
  21. }

adapter.jpg

StateWith adapter, the wrapper and the wrapped are different but forced to seem alike. All the functions in the target interface (IModule) must be defined, but there is no preordained role for the adapted object. Some function calls might be ignored, others might be intercepted and re-directed.

With state, the wrapper and the wrapped are already alike because they implement the same interface, and every single method is directly mapped to the current state.

state.jpg

Decorator. The Decorator pattern takes wrapping to an extreme, wrapping one object multiple times:

decorator.jpg

As you can see, as with State, the wrapping assumes that the wrapping object and the wrapped object implement the same interface. But in this case, the steady, unique object is not the wrapping object, but the wrapped object. Think of the decorator pattern as the state pattern turned inside out, many times over.

Decorator is useful for piling on incremental modifications to an object's behavior.

Suppose I needed to change the nature of a BaseNavigable. To start, I'd write a NavigableWrapper class like the ModuleWrapper class above. This class would just steps through the INavigable interface and implement its methods in functions like this:

Actionscript:
  1. public function addChild(...childModules)
  2. {
  3.     innerNavigable.addChild(...childModules)
  4. }

Now suppose that, while the application was running, it suddenly becomes necessary to know when the last child of a module with children had been selected. Perhaps the user has just changed their preference, and now wants a comment box to appear when they've finished a module, or perhaps the database wants to know how long the user lingered there.

Navigables do contain the necessary information (index==numChildren-1) but don't advertise it.

The first step to getting navigable to shout out its terminal position is to subclass Navigable Wrapper and override just one function:

Actionscript:
  1. class TellEndNavigableDecorator extends NavigableWrapper
  2. {
  3.     public override function navigate(index:int)
  4.     {
  5.         //added responsibilities
  6.         if (index=innerNavigable.numChildren-1)
  7.         {
  8.             innerNavigable.dispatchEvent (new Event ("atEnd"));
  9.         }
  10.         //relay call to ultimate destination
  11.         innerNavigable.navigate(index)
  12.     }
  13. }

Now, when this information becomes necessary, decorate the module:

Actionscript:
  1. aModule.navigable=new TellEndNavigableDecorator (aModule.navigable);

After this point, when calls are made on navigable, they are relayed to the original _navigable, with this crucial difference: goto calls are examined, and if the index is the last, a message is dispatched.

The exhilirating thing about Decorators is that there is no logical end to the wrapping process:

Actionscript:
  1. //successive decorations
  2. aModule.navigable=new TellEndNavigableDecorator (aModule.navigable);
  3. aModule.navigable=new LoopAtEndNavigableDecorator (aModule.navigable);
  4. aModule.navigable=new ContactLoggerNavigableDecorator (aModule.navigable);

inteventions.jpg

Imagine trying to get a comparable level of flexibility by using parameters or subclassing. Now stop imagining! You don't have to enter that hell, because you have the Decorator Pattern in your tool kit.

I've hardly used these wrapper patterns, so I'll stop here...but you must admit they are goofy fun.

Tags: patterns · Flash2 Comments

Leave A Comment

2 responses so far ↓