Building Pet 4: Interfaces for Loading

I was acquainted with interfaces before I read Head First Design Patterns, but their full glory was hidden from me.

Here was a typical use case for me beforehand.

An application would keep a reference to an object that it would occasionally manipulate:

[as]
//declare a var in the app
protected var selectedModule:BaseModule;
//later that day….
selectedModule=aModule;
selectedModule.navigable.navigate(5);
[/as]

This would work out for a while. But the day would inevitably come when I needed a strange module, a module differing from BaseModule across the board. Making it a subclass of BaseModule would mean overriding nearly all its methods, an absurdity.

Then it was time for an interface! Here is an interface for modules:

[as]
//interface names usually start with “I”
public interface IModule
{
function set moduleName(aName:String):void;
function get moduleName():String;
function set moduleParent(aModule:IModule):void;
function get moduleParent():IModule;
function set siblingIndex(aIndex:uint):void;
function get siblingIndex():uint;
function set wakeable(aWakeable:IWakeable):void;
function get wakeable():IWakeable;
function set navigable(aNavigable:INavigable):void;
function get navigable():INavigable;
function set loadable(aLoadable:ILoadable):void;
function get loadable():ILoadable;
function set controllable(aControllable:IControllable):void;
function get controllable():IControllable;
function set alertable(aAlertable:IAlertable):void;
function get alertable():IAlertable;
}
[/as]

The IModule interface ensures that all modules have their essential strategies and properties via getter/setters. Any object that implements IModule can be relied upon to compose these essentials. Whether the module descends from BaseModule no longer matters.

[as]
//declaration–interfaces can type variables, just like classes
protected var currentModule:IModule;
//later that day…
currentModule=new StrangeModule();
//the application can rely on the fact that the currentModule has a navigable property
if (aModule.navigable!=null)
{
aModule.navigable.navigate(5);
}
[/as]

If you’ve never used interfaces, you might wonder how the application trusts a StrangeModule instance to act module-like. That’s a grammatical matter: the StrangeBeastModule class advertises its capabilities by “implementing” the interface in its class declaration.

[as]
//no mention of BaseModule here
//because this class implements an interface instead of extending BaseModule
class StrangeBeastModule implements IModule
{
function get moduleName():String
{
return “AlwaysTheStrangeOne”;
{
//now the rest of the interfaces methods MUST be written
//otherwise the application file that includes this class will not compile

}
[/as]

Because of the IModule interface, the application can rely on all IModule-implementing objects to identify themselves promptly:

[as]
//myBlankModule
trace (aModule.moduleName);
//AlwaysTheStrangeOne
trace (aStrangeModule.moduleName)
[/as]

Notice that the strategy objects stipulated by IModule are themselves typed by interfaces:

[as]
//in the IModule interface
function set loadable(aLoadable:ILoadable):void;
[/as]

Let’s examine ILoadable. This is an interface I actually planned rather than an escape hatch I used after coding myself into a corner.

The more-typical ILoadable interface includes real public methods as well as getters/setters:

[as]
public interface ILoadable
{
//ready to go?
function get isReady():Boolean;
function set isReady(ready:Boolean):void;
//completely loaded?
function get isLoaded():Boolean;
function set isLoaded(loaded:Boolean):void;
//if the load is done in stages, what stage?
function get index():int
//control loading
function load():void;
function close():void;
//keeping tabs
function addReadyListener(handler:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void;
function removeReadyListener(handler:Function):void;
function addProgressListener(handler:Function):void;
function removeProgressListener(handler:Function):void;
function addLoadListener(handler:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void;
function removeLoadListener(handler:Function):void;
//what actually gets loaded
function get content():*;
}
[/as]

Consider the advantages I get from typing _loadable with ILoadable rather than BaseLoadable.

Flexibility. When it comes to loading, a Flash developer’s work is never done. There’s always going to be a late-developing wrinkle or specification, and a base loading class was never in the cards. There’s just too many different types of loads (swfs, pictures, text, xml, video, sound). With an interface, I don’t need this base class…I can introduce any number of loading classes, at any time. As long as they implement ILoadable, the application should be able to squeeze a load from them.

The extra beauty of interfaces is that they accommodate rather then displace the inheritance model. I could, in fact, grow an entire branching library of loadables from a BaseLoadable class, and, as long as BaseLoadable implements ILoadable, the branches will inherit that implementation. So if you have the slightest suspicion that inheritance might not be enough, it’s worth it to write an interface. Interfaces are usually not much work to write, and an interface won’t interfere while you give frantic subclassing a try.

Code modified in one place. Imagine that your application did not require that its loading classes implement ILoadable. Imagine that you now introduce a new loading class, AmazonResultsLoadable, with a loadResults() method.

Now your situation is not nice at all. In addition to writing the new class, you have to rewrite the part of your application that handles the loading. It has to be made aware of this new class and its little idiosyncrasies, like its cutesy insistence on being told to loadResults() rather than load(). What a pain!

[as]
if (loadable.type==”AmazonResults”)
{
loadable.loadResults();
}
[/as]

Making one modification is no biggie, of course. But how many modifications will there be? The more there are, the longer the debugging takes, especially because code in two different places needs to be coordinated.

Interface Discipline

Of course, it takes sweat and ingenuity to make a menagerie of very different objects look and act the same…and if you are learning interfaces, I guarantee that at some point you will your discipline will break down.

For me, struggling to shoehorn all loadables into the ILoadable interface, the most tempting shortcut was to add a new method to the interface.

When you feel the urge to expand the interface, resist! Because:

1) Every method you add to the interface has to be implemented by every class that implements ILoadable. Early in the game, adding those (often useless) methods seems acceptable, but later on, after the list of ILoadable-implementing classes increases, you will end up updating and padding an unacceptable number of classes.

2) Mission creep dilutes the essence of the interface, making it harder to read and less portable to other projects.

A sure sign of broken discipline is a method whose intention can’t easily be inferred from the context. Take for instance these methods:

[as]
//in IModule
function get siblingIndex(aIndex:uint):void;
[/as]

I know what this methods delivers, but I have no idea why this information was ever needed, or where it is used.

[as]
//if the load is done in stages, what stage?
function get index():int
[/as]

Even my cryptic comments confuse me here…what is that God-cursed loabable index? It irks me every time I see it, but I’m not gonna be a hero and investigate at this point.

Polymorphic Loadables

In terms of wonderfulness, every benefit to interfaces covered so far is piddling compared to the benefits of interface-enforced polymorphism.

Polymorphism means being able to treat different objects in the same way. So far we’ve been concerned with polymorphism over time, especially how new classes can be introduced into an application without refactoring…but traditional “spatial” polymorphism is the kind that happens when your well-oiled application actually runs.

Let’s look briefly at ILoadable’s varied implementations before considering polymorphism in action.

The interface’s signature method load covers a lot of territory:

[as]
//DisplayLoadable for pictures
//has an instance of Loader that does the loading
public function load():void
{
var request:URLRequest=new URLRequest(url);
loader.load(request);
//listen to it….
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoad);
}

//CompiledDisplayLoadable for swfs
//subclasses DisplayLoadable, adds a context argument for cross scripting
public override function load():void
{
//allow cross-coding
var context:LoaderContext= new LoaderContext(false, ApplicationDomain.currentDomain);
var request:URLRequest=new URLRequest(url);
loader.load(request, context);
//listen to it….
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoad);
}

//SoundLoadable for sound
//has an instance of Sound that does the loading
public function load():void
{
sound.load(request)
timer.addEventListener(TimerEvent.TIMER, checkReady);
timer.start();
}

//VideoLoadable for video
//an instance of NetStream does the loading
public function load():void
{
if (open)
{
loaderStream.resume();
} else {
loaderStream.play(url);
open=true;
}
}

//DataLoadable for data
//an instance of URLLoader does the loading
public function load():void
{
var request:URLRequest=new URLRequest(url);
loader.load(request);
}
[/as]

As you can see, with its cleanly reorganized APIs, AS3 practically writes the implementations of ILoadable’s load for us.

Now let’s look at the addReadyListener implementations. This was a bit trickier. For most loadable strategies, the _isReady value is identical to the _isLoaded value…but I tried mightily to shoehorn the streaming loadables into the ILoadable interface, and generally, ready for the them means buffered, not loaded.

[as]
//DisplayLoadable
//addReadyListener just defers to addLoadListener
//since they are equivalent
public function addReadyListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
{
addLoadListener(handler, useCapture, priority, useWeakReference);
}
public function addLoadListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
{
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, relayEvent, useCapture, priority, useWeakReference);
addEventListener(Event.COMPLETE, handler);
}

//CompiledDisplayLoadable
//addReadyListener for this subclass listens to Event.INIT, not Event.COMPLETE
//because only at INIT does cross-scripting become possible
public override function addLoadListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
{
loader.contentLoaderInfo.addEventListener(Event.INIT, relayEvent, useCapture, priority, useWeakReference);
addEventListener(Event.INIT, handler);
}

//VideoLoadable
//ready is dispatched when buffered
public function addReadyListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
{
addEventListener(“ready”, handler);
progressClock.start();
}
//later that day, inside the progressClock’s timer callback…
if (loaderStream.bufferLength==loaderStream.bufferTime)
{
if (_isReady==false)
{
_isReady=true;
dispatchEvent(new Event(“ready”));
}
}
[/as]

Aral Balkan has proposed that, just as AS3 allowed coders to put getter/setters into interfaces, AS4 should allow coders to put events like “ready” or “complete” inside the interface. I’ll sign up for that. Interfaces like ILoadable would be less cluttered, because they wouldn’t have to duplicate the functionality of EventDispatcher.

Still, for the sake of clarity, in this case I prefer having clunky methods like addProgressListener, addReadyListener and addLoadListener spelled out in the interface. It makes it blatant that I can take any loadable, any time, and listen for specific events:

[as]
//inside the module
_loadable.addLoadListener(initContent);
//in the application
selectedModule.loadable.addProgressListener(progressBarExpands);
selectedModule.loadable.addReadyListener(newSelectionWakes);
selectedModule.loadable.addLoadListener(downloaderGoesToNextDownload);
[/as]

The Power of Polymorphism

It takes work to ensure polymorphism. What is the payoff?

For my playground, it is huge:

1) I can develop modules with different loading requirements more quickly because the vocabulary for loading is universal and simple.

2) I can more easily develop modules that mix media, because each media’s loading requirements are the same.

3) Because they appear to be identical, loadables can be grouped and sliced and diced at will, so a smart, user-sensitive background loading queue becomes possible.

In the next two posts, we’ll see further payoffs. For now, I’ll just suggest that the connection between interfaces and design patterns is profound.

To make something seem like something else seems like a trick, a decent trick but one whose limits are reached once we’ve managed to get a bunch of different things to act as if they were the same exact thing. But keep in mind that this trick goes directly to the core of any object-oriented language.

An object IS something and SEEMS like something else. If you’ve read this far, this fact is already supremely important to you. The autonomy of the object keeps what it IS safe from being overwritten or manhandled. And its class or interface keeps what it SEEMS to be constant, so it remains portable and predictable.

Design patterns often push this trick to a whole new level, creatively structuring the relation of objects on the basis of the discrepancy of IS/SEEMS, which, like a simple melody elaborated by a jazz musician, is endlessly diverting.

One comment.

  1. HI,

    your site rocks …..and one more request.. we need some small examples for the design patters that would be pretty useful for peoples like me to learn nice OOP way of programming …

    thanks again

Post a comment.