I remember vividly the first time I opened up the Actionscript panel in the Flash IDE and wrote one line in the heretofore untouched panel:
-
myApp=new Application(_root);
It was a special moment because this lightning-stab of a line both compressed and expressed a week of work. Ctrl-Enter...and...SWISH!
Between that moment and moment I cracked open Head First Design Patterns, I learned surprisingly little about application files per se.
I did apply to them what I had learned more generally about coding--that it's safest to work on one part first, test it, then build a second part, test it, relate it to the first part, test that relation, etc. Or be "agile," as the Conventional Wisdom intones. (Especially important if your IDE is worthless...run scared, run very scared! Take only quick little hamster steps!)
But as for how to structure application files...their components were different, their components were juxtaposed in different ways...what else to say?
If you've ever shared my ignorance, I'll bet you ran into this problem:
1) You created a component.
2) You created another component.
3) You gave the second component a reference to the first one.
4) You repeated this process till all the components were built.
5) Debugging the application file, you changed the order of component creation, whereupon the application failed because the components are linked by daisy-chained references established in a particular order, which you (somehow) violated.
Is your constructor code fragile like this? If so, your application file needs a dose of the Mediator pattern.
Mediator Graphed
Here is my playground's application file and components, with just a few of the interactions that make it hum:
Before learning Mediator, I had two strategies for structuring these interactions.
Object reference. The constructor function in my applications was often like a chaotic Christmas morning, with everyone giving everyone else a picture of themselves. You can still see a remnant of this strategy in this current method:
-
protected function makeComponents():void
-
{
-
aBox=new AlertBox();
-
moduleTree=new ModuleTree();
-
//note the object references
-
//OBJECT REFERENCE!!
-
moduleTree.setBaseModule(this);
-
//OBJECT REFERENCE!!
-
dloader=new Downloader(moduleTree);
-
progressBar=new ProgressBar();
-
history=new History();
-
controlBar=new ControlBar();
-
}
Imagine that, instead of a total of two object references, every component had at least one, and often more than one. Imagine further debugging an interaction involving three of four components. I would have to follow the interaction though each object and see how each used or abused its fellows in its interior. No fun.
And what if I wanted to create another site by subclassing the application file, a site where the queueing logic was not tied to the moduleTree, or the controls were decentralized, or AlertBox became AlertColumn? With the code above, these are done with relative confidence; with Christmas-morning code, teeth would grind.
The event model. After AS3 rolled out with its new radically simplified event model (no delegates, no callbacks, no watches), I really internalized events for the first time. There were no great new powers...but the clarity felt like a great new power. Syntax really does matter.
I thought my new comfort with events might make application files easier to structure. And it did. Instead of relating by references, objects could listen to each other. So, for instance, the controlBar could listen to the history to know whether to enable its back button, and the history could listen to the controlBar for new history navigations. Events made adhering to gospel of decoupling more possible than ever.
But the event model only solved the the most obvious dependency problems. Too often, it still left component interactions scattered across the application, and event-based mediation struggled to structure interactions that involved three or more components in quick succession.
Enter Mediator to pull things together.:
A mediator object gets a reference to all an application's components, and...you guessed it...mediates them. Instead of the components relating to each other, they relate to the mediator, who handles all their interactions for them. A mediator is like the harried mother in a big Irish family; siblings don't talk about serious matters because the mother does the heavy lifting, putting siblings in contact when appropriate and moving plans forward.
Mediating New Selections
About 70% of the interactions in my playground's Mediator object handle navigations. A SelectModuleEvent is the primary instigator:
-
public function onNewFocus(e:SelectModuleEvent=null):void
-
{
-
//if a pre-selected module has not loaded before it has been superceded
-
if (loader!=null)
-
{
-
loader.removeReadyListener(onReady);
-
loader.removeProgressListener(onProgress);
-
}
-
//prepare for new module
-
selectedEvent=e;
-
loadingModule=e.mod;
-
//the downloader will begin downloading according to the new focus
-
dloader.onNewFocus(loadingModule);
-
//all navs should be checked for loads, even historical navs
-
checkIfLoaded(e.mod);
-
}
-
private function onReady(e:Event):void
-
{
-
loader.removeReadyListener(onReady);
-
loader.removeProgressListener(onProgress);
-
progressBar.showProgress(null);
-
dloader.onNewFocus(loadingModule);
-
shiftFocus();
-
}
When the module is loaded, the focus shifts for real. Here are excerpts from a long shiftFocus method:
-
oldSelectedModule=selectedModule;
-
selectedModule=loadingModule;
-
oldMasterModule=masterModule;
-
masterModule=moduleTree.getMasterModule(loadingModule);
A masterModule provides the controls for the control bar. If the selected module has a controllable strategy where isMasterModule==true, it becomes the masterModule, otherwise the masterModule is the first module above the selectedModule where this is the case. A slide module inside a slideshow module, for instance, does not have its own controls. Those belong to the slideshow itself.
-
controlBar.setControls (selectedModule, masterModule);
The fact that some child modules, like a slide in a slideshow, will need their parent modules to stay awake while they gain focus complicates what would otherwise be the simple shutting down of oldSelectedModule and starting up of selectedModule:
-
//sleeping and waking
-
//don't put to sleep a module that's no longer selectedModule if it's the masterModule;
-
if (oldMasterModule!=null&&oldMasterModule.wakeable!=null&&oldMasterModule!=masterModule&&oldMasterModule!=selectedModule)
-
{
-
oldMasterModule.wakeable.sleep();
-
}
-
if (oldSelectedModule!=null&&oldSelectedModule.wakeable!=null&&oldSelectedModule!=masterModule&&oldSelectedModule!=selectedModule)
-
{
-
oldSelectedModule.wakeable.sleep();
-
}
-
//redundant wakes are ignored by wakeable
-
if (masterModule.wakeable!=null)
-
{
-
masterModule.wakeable.wake();
-
}
-
if (selectedModule.wakeable!=null)
-
{
-
selectedModule.wakeable.wake();
-
}
When a module is selected, the mediator listens to it for new navigations, so the whole cycle can start over:
-
if (aModule.navigable!=null)
-
{
-
//navigate
-
aModule.navigable.addEventListener("selectModule", onNewFocus);
-
//for back-button history
-
aModule.navigable.addEventListener("onCommand", onFreshNav)
-
}
It listens as well for various alert events, if the selected module has an alertable strategy:
-
//a regular alert
-
aModule.alertable.addEventListener("alert", onAlert);
-
//if a module sets a default alert
-
aModule.alertable.addEventListener("defaultAlert", onDefaultAlert);
-
//form alerts are stuck until the close button is pressed
-
aModule.alertable.addEventListener("stuckAlert", onStuckAlert);
-
//alerts are usually handled in series during a pull event
-
aModule.alertable.addEventListener("startAlerts", onStartAlerts);
-
aModule.alertable.addEventListener("stopAlerts", onStopAlerts);
The complete mediator fits snugly into my application file:
-
//all components already created
-
mediator=new Mediator(this);
An application file with a mediator object should type all its component properties as internal. That way, if the mediator class is in the same package as the application class, it can access these components without asking for them. The mediator is by definition quite intimate with the application. (For more on the internal access modifier, see this post.)
Advantages of Mediator
Now, chances are, the Mediator pattern is no revelation to you. Isn't it just ye old Application class?
True enough. Mediator is definitely not one of those mind-blowing patterns you never would have discovered solo. But consider that deliberately partitioning off the specifically interactive parts of your application from the configuring and constructing part of it renders it easier to write, debug and modify.
If I were to follow this partitioning logic out, I might split up the mediator class itself. Since most of the code deals with new selections, I could create a SelectionMananger class along with a AlertManager class, and ship stray bits of interactivity like this back to the application itself:
-
protected function onRightClick(e:Event):void
-
{
-
shell.contextMenu.customItems=controlBar.getMenuItems();
-
}
When I assemble new versions of this playground, portability will become an issue, and that will probably happen.
But I didn't dismember Mediator, and here's why.
I went through a phase last year when the classes I was writing became so clear and self-explanatory that the number of comments fell off drastically. I became every painfully scrupulous about naming, and obsessed with writing functions no longer than twenty-five lines. This kind of code made me giggle with glee:
-
JackAndJill();
-
WentUpTheHill();
-
ToFetch();
-
APailOfWater();
After a while, though, I realized that reading this code afterwards was like reading Wittgenstein or other analytic philosophers. Every individual sentence seemed to make rock-solid British common sense. But the successions of these simple sentences quickly became bewildering...by the fourth sentence, I was often lost:
-
GoodAfternoon();
-
FromRussiaWithLove();
-
TheGreenestTea()
-
Later();
Then I made a resolution: if the code is complex, let it seem complex. Don't hide the complexity just to hide complexity.
For a Flash coder, their mediator files are going to be their most complex. Not only do they herd a bunch of components together, they herd them while taking into account that users might interrupt or reconfigure its mediations three times in a second. There is no getting around this complexity, you might as well dump it into Mediator.
It's more than quarantining. Even if you have an IDE like FlexBuilder that has declaration jumping and debugger stepping to help untangle interactions, a quick scroll of the whole mediating mess is indispensible. It can be damn useful just having that tangled pile right in front of you.
Tags: patterns · codeNo Comments


0 responses so far ↓
There are no comments yet...Kick things off by filling out the form below.