Pet Theory header image 2

Building Pet 4: Interfaces for Loading

May 8th, 2007 by matt

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:

Actionscript:
  1. //declare a var in the app
  2. protected var selectedModule:BaseModule;
  3. //later that day....
  4. selectedModule=aModule;
  5. selectedModule.navigable.navigate(5);

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:

Actionscript:
  1. //interface names usually start with "I"
  2. public interface IModule
  3. {
  4.     function set moduleName(aName:String):void;
  5.     function get moduleName():String;
  6.     function set moduleParent(aModule:IModule):void;
  7.     function get moduleParent():IModule;
  8.     function set siblingIndex(aIndex:uint):void;
  9.     function get siblingIndex():uint;
  10.     function set wakeable(aWakeable:IWakeable):void;
  11.     function get wakeable():IWakeable;
  12.     function set navigable(aNavigable:INavigable):void;
  13.     function get navigable():INavigable;
  14.     function set loadable(aLoadable:ILoadable):void;
  15.     function get loadable():ILoadable;
  16.     function set controllable(aControllable:IControllable):void;
  17.     function get controllable():IControllable;
  18.     function set alertable(aAlertable:IAlertable):void;
  19.     function get alertable():IAlertable;       
  20. }

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.

Actionscript:
  1. //declaration--interfaces can type variables, just like classes
  2. protected var currentModule:IModule;
  3. //later that day...
  4. currentModule=new StrangeModule();
  5. //the application can rely on the fact that the currentModule has a navigable property
  6. if (aModule.navigable!=null)
  7. {
  8.       aModule.navigable.navigate(5);
  9. }

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.

Actionscript:
  1. //no mention of BaseModule here
  2. //because this class implements an interface instead of extending BaseModule
  3. class StrangeBeastModule implements IModule
  4. {
  5.     function get moduleName():String
  6.     {
  7.         return "AlwaysTheStrangeOne";
  8.     {
  9.     //now the rest of the interfaces methods MUST be written
  10.     //otherwise the application file that includes this class will not compile
  11.     ...
  12. }

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

Actionscript:
  1. //myBlankModule
  2. trace (aModule.moduleName);
  3. //AlwaysTheStrangeOne
  4. trace (aStrangeModule.moduleName)

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

Actionscript:
  1. //in the IModule interface
  2. function set loadable(aLoadable:ILoadable):void;

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:

Actionscript:
  1. public interface ILoadable
  2. {
  3.     //ready to go?
  4.     function get isReady():Boolean;
  5.     function set isReady(ready:Boolean):void;
  6.     //completely loaded?
  7.     function get isLoaded():Boolean;
  8.     function set isLoaded(loaded:Boolean):void;
  9.     //if the load is done in stages, what stage?
  10.     function get index():int
  11.     //control loading
  12.     function load():void;
  13.     function close():void;
  14.     //keeping tabs
  15.     function addReadyListener(handler:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void;
  16.     function removeReadyListener(handler:Function):void;
  17.     function addProgressListener(handler:Function):void;
  18.     function removeProgressListener(handler:Function):void;
  19.     function addLoadListener(handler:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=true):void;
  20.     function removeLoadListener(handler:Function):void;
  21.     //what actually gets loaded
  22.     function get content():*;
  23. }

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!

Actionscript:
  1. if (loadable.type=="AmazonResults")
  2. {
  3.     loadable.loadResults();
  4. }

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:

Actionscript:
  1. //in IModule
  2. function get siblingIndex(aIndex:uint):void;

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

Actionscript:
  1. //if the load is done in stages, what stage?
  2. function get index():int

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:

Actionscript:
  1. //DisplayLoadable for pictures
  2. //has an instance of Loader that does the loading
  3. public function load():void
  4. {
  5.     var request:URLRequest=new URLRequest(url);
  6.     loader.load(request);
  7.     //listen to it....
  8.     loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoad);
  9. }
  10.  
  11. //CompiledDisplayLoadable for swfs
  12. //subclasses DisplayLoadable, adds a context argument for cross scripting
  13. public override function load():void
  14. {
  15.     //allow cross-coding
  16.     var context:LoaderContext= new LoaderContext(false, ApplicationDomain.currentDomain);
  17.     var request:URLRequest=new URLRequest(url);
  18.     loader.load(request, context);
  19.     //listen to it....
  20.     loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoad);
  21. }
  22.  
  23. //SoundLoadable for sound
  24. //has an instance of Sound that does the loading
  25. public function load():void
  26. {
  27.     sound.load(request)
  28.     timer.addEventListener(TimerEvent.TIMER, checkReady);
  29.     timer.start();
  30. }
  31.  
  32. //VideoLoadable for video
  33. //an instance of NetStream does the loading
  34. public function load():void
  35. {
  36.     if (open)
  37.     {
  38.         loaderStream.resume();
  39.     } else {
  40.         loaderStream.play(url);
  41.         open=true;
  42.     }
  43. }
  44.  
  45. //DataLoadable for data
  46. //an instance of URLLoader does the loading
  47. public function load():void
  48. {
  49.     var request:URLRequest=new URLRequest(url);
  50.     loader.load(request);
  51. }

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.

Actionscript:
  1. //DisplayLoadable
  2. //addReadyListener just defers to addLoadListener
  3. //since they are equivalent
  4. public function addReadyListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
  5. {
  6.     addLoadListener(handler, useCapture, priority, useWeakReference);
  7. }
  8. public function addLoadListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
  9. {
  10.     loader.contentLoaderInfo.addEventListener(Event.COMPLETE, relayEvent, useCapture, priority, useWeakReference);
  11.     addEventListener(Event.COMPLETE, handler);
  12. }
  13.  
  14. //CompiledDisplayLoadable
  15. //addReadyListener for this subclass listens to Event.INIT, not Event.COMPLETE
  16. //because only at INIT does cross-scripting become possible
  17. public override function addLoadListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
  18. {
  19.     loader.contentLoaderInfo.addEventListener(Event.INIT, relayEvent, useCapture, priority, useWeakReference);
  20.     addEventListener(Event.INIT, handler);
  21. }
  22.  
  23. //VideoLoadable
  24. //ready is dispatched when buffered
  25. public function addReadyListener(handler:Function, useCapture:Boolean=false, priority:uint=0, useWeakReference:Boolean=true):void
  26. {
  27.     addEventListener("ready", handler);
  28.     progressClock.start();
  29. }
  30. //later that day, inside the progressClock's timer callback...
  31. if (loaderStream.bufferLength==loaderStream.bufferTime)
  32. {
  33.     if (_isReady==false)
  34.     {
  35.         _isReady=true;
  36.         dispatchEvent(new Event("ready"));
  37.     }
  38. }

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:

Actionscript:
  1. //inside the module
  2. _loadable.addLoadListener(initContent);
  3. //in the application
  4. selectedModule.loadable.addProgressListener(progressBarExpands);
  5. selectedModule.loadable.addReadyListener(newSelectionWakes);
  6. selectedModule.loadable.addLoadListener(downloaderGoesToNextDownload);

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.

Tags: patterns · code1 Comment

Leave A Comment

1 response so far ↓

  • 1 saravanan Oct 19, 2007 at 6:59 am

    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