Advertisement
  1. Code
  2. JavaScript

AS3 101: OOP - Introduction to Interfaces

Scroll to top
46 min read
This post is part of a series called AS3 101.
Everything You Could Possibly Want to Know About Import Statements*
AS3 101: OOP - Introducing Design Patterns

In the closing chapters of our Object-Oriented Programming series, we will take a look at some more advanced concepts, focusing on interfaces (the programmatic kind).

This may be the final installment, but we'll be splitting the content into two parts. In this part, we'll get an introduction to interfaces, and begin to use them in small projects. In the next part, interfaces will play a key role in a bigger, more practical application: a simple drawing program.


(There Is No) Final Result Preview

The projects that we'll be working on are many and small. The functionality contained therein is purposely kept to a minimum, so as to focus on the concepts and not get lost in programmatic details.

If you're jumping into this series at this article, I recommend that you make sure you're up to speed with the rest of the concepts introduced in not only the previous Object-Oriented Programming tutorials, but the core concepts of the rest of the AS3 101 series. This material is not exactly for the faint of heart, but if you feel confident you know that other material well, you won't have any problems. Just stick with me, and you'll learn a few things.


Step 1: The Interface

You won't be begrudged for thinking that, when I mention the "interface", I'm talking about the UI of a program, on-screen. And while that is, indeed, an interface, we're here to discuss a more advanced Object-Oriented topic of the same name.

Imagine, if you will, a music studio, with lots of electronic gear (for our purposes, the more discrete pieces of equipment the better; note of this all-in-the-computer-box music stuff).

A music studio with lots of gearA music studio with lots of gearA music studio with lots of gear

Photo from Flickr user The Cowshed and is distributed under a Creative Commons Attribution-NoDerivs 2.0 Generic license

In the end, all of these disparate noise makers need to get routed to the noise recorders, and then the recording gets played back and recorded as a mix. A lot of audio needs to get from point A to point B to point C, with lots of points in between.

Now, as you probably know, sound – in electronic form – travels from one point to another via a cable. Plug one end of the cable into the thing producing sound, and the other end of the cable into the thing receiving the sound, and you have a signal.

Imagine how difficult this would be if each musical equipment manufacturer created its own type of plug for the cables. You'd be stuck with only equipment from a single manufacturer, or possibly with several dozen adapters lying around to connect these incompatible inputs and outputs.

Fortunately, there are a few standards to which all manufacturers adhere. This is a bit of a gross simplification, but nearly all audio jacks you'll find in a music studio will be one of two kinds: XLR or quarter-inch (audiophiles, let's ignore impedance and balancing, as well as other jacks like RCA). Thankfully, with only two types of cable, and the occasional adapter, you can connect virtually any piece of audio gear to any other.

The way in which a piece of gear connects to the rest of the world is its interface. In a generic sense, you can see how this applies to user interfaces, as well – it's how the software connects to the outside world (i.e., the user). But the audio interface for a mixer or keyboard is that piece of gear's particular capabilities for input and output using which kind of plug.

A Patch Bay, used to interconnect many different pieces of gear in a studioA Patch Bay, used to interconnect many different pieces of gear in a studioA Patch Bay, used to interconnect many different pieces of gear in a studio

Photo from Flickr user The Cowshed and is distributed under a Creative Commons Attribution-NoDerivs 2.0 Generic license


Step 2: Bringing It Back to Programming

In the same way as audio equipment, the interface of an object is the way in which it connects to the outside world. Now, "connect" is probably not the best word to use. Let's use "makes itself available" instead.

All objects will have an interface, whether you think about it or not. You can think of the interface to an object as all of the public methods and properties belonging to that object. Put another way, the interface of an object is the stuff you can do to that object.

So when you write a class, and define four public methods, three private methods, and two protected methods in it, the interface to the objects is made up of the four public methods.

You have probably heard of the acronym "API". It's quite the rage these days in the form of web service APIs, such as the Flickr API or the Google Maps API. API stands for "Application Programming Interface." In this sense, the interface is the set of methods you can call on the web service, defining not only the name of the methods, but the parameters you can send to it and the type of data that will be returned.


Step 3: Implementations

Let me pause briefly and bring up encapsulation again (one of the core principles of Object-Oriented Programming – see the first part of the OOP series for more information). Note that the interface is not made up of private, protected, or internal methods and properties. Those members are hidden away from the outside world, vis-a-vis, not part of the interface.

Similarly, it's important to understand a subtle distinction between the interface, as seen from the outside world, and the implementation. That is, the interface defines the method signature (a method signature denotes the method name, its arguments and type, and the return type), like this: function publicMethod(arg1:String):Boolean. However, what actually goes on inside of the method is not the concern of the interface. That's up to the implementation.

If you were enjoying the audio equipment analogy, you can think of this differentiation between interface and implementation as the difference between putting a 1/4-inch jack on a piece of gear, and the components and quality of material put into that jack. Not all 1/4-inch jacks are created equal; some are gold-plated while others are nickel-plated. Some will have high-quality wires soldered to it, others will be molded directly onto a circuit board. The implementation (materials and method of construction) is not under the jurisdiction of the interface (a jack that is 1/4-inch wide).

If you weren't enjoying the audio equipment analogy, I apologize for bringing it up again.


Step 4: Interfaces in Code

As it happens, I'm not bringing up all of this just to tell you that your objects already have an interface. That's important to realize, but what we're after right now is another Object-Oriented Programming concept known as the interface. You can actually define an interface without defining the implementation. If you think about it, this is inherently what the interface is about. The interface merely defines what you can do with something, not how things get done.

Confused yet? If not, you're not thinking about it hard enough! Just teasing, but I do not expect this to make complete sense at this point. Let's move on to a simple, perhaps useless, but definitely hands-on example.


Step 5: Writing an Interface

Let's crack open Flash and the text editor of your choice, and try our hand at creating an interface. Before we can really begin, create a folder on your computer in which to house this mini-project. It'll just be tidier to have a dedicated project folder. All files that we create in this step will be saved in this folder.

Now, create a new .as file. I'll assume you're using an editor that doesn't have much in the way of templates, but if you're using Flash Builder, you can get most of this by choosing "ActionScript Interface" from the new file menu. And of course, many other editors have templates (even Flash CS5 has an ActionScript 3.0 Interface template). But it's good to learn the nuts and bolts of these templates.

Save your file as "ITest.as" in the project folder. It's not required, but it's common practice to name your interfaces following the convention of naming classes (i.e., capital first letter, camel-case after that), with the addition of an "I" at the front. The "I" indicates that the datatype is an interface, rather than a regular class. Flash will happily work with interfaces that are named without the leading "I" but it's a convention that you'll find handy for your own projects, as well as useful when looking at other developers' classes and interfaces.

Now, enter the following boiler plate code:

1
2
package {
3
    public interface ITest {
4
5
    }
6
}

You've just written an interface (one that doesn't define much, but still). Note that if we wanted to utilize a package structure, we'd spell it out after the package keyword just like in classes. And except for the use of interface, the definition is identical to a class definition. However, note that we don't put in a constructor. The constructor is a special method, and isn't really part of an interface. Any other method, though, we can define in the interface, and we'll do that next. Add the highlighted line to your interface file:

1
2
package {
3
    public interface ITest {
4
        function test():void;
5
    }
6
}

Now, we've declared that the ITest interface has one public method. It's called test, takes no arguments, and returns nothing. What does it do? The interface isn't terribly concerned with that. What it does will be up to the concrete implementation. Notice that the method signature ends with a semi-colon, and not curly braces.

Also notice the lack of public or private or anything in front of the function. The exceptionally quick-witted will recall that omitting the access modifier in a class will default to internal. Since interfaces define public methods only, the default is public. In fact, even trying to specify public will cause errors.

Save your file, and your interface is now usable. We'll use it in just a moment.


Step 6: But First, a Quick Summary

To put the previous hands-on instruction into theory, the mechanics of an interface in OOP is rather similar to a class:

  • An interface is defined in its own text (.as) file, just like a class.
  • The boiler plate code is nearly identical to that of a class file; you use pacakges and imports as needed.
  • The name of the interface must be the same as the base name of the file.
  • The interface is a datatype, just like a class.

There are, as you'd expect, differences. The biggest difference is this:

Interfaces do not have any functionality.

There are other differences, some of which we haven't touched on yet:

  • In the boiler plate, you use the keyword interface instead of class
  • You can define public methods only
    • This does include implicit setters and getters — e.g., function get someString():String
    • This does not include private, protected, or internal methods.
    • This does not include properties of any kind.
  • When defining these public methods, you do not use the public keyword.
    • Not only is public assumed, it's actually non-compilable code to use that keyword in the method signature.
  • Seeing as how there is no functionality, you end the method declaration with a semi-colon, and omit the curly braces that normally
    wrap around the method implementation.

Step 7: Implementing an Interface

It's not enough to simply define an interface; the interface needs to be implemented in order to be useful. What implements an interface? A class, naturally. Any class you write can technically implement any interface you choose. We'll take a look at how this works in this step.

This class is often called the "implementation" when referred to in relation to the interface. It may also be called a "concrete" class; as in, it's something "solid" and "real" as opposed to the non-functional interface.

Create a new text file called "TestImplementation.as" and save it into the project folder. It will be a class, so add the following basic class skeleton:

1
2
package {
3
    public class TestImplementation {
4
        public function TestImplementation() {
5
        }
6
    }
7
}

So far, nothing new or even exciting. It's just a class. It has no connection to the ITest interface we created in the last step. But obviously we want to utilize that interface, so here's what we need to do: add implements ITest after the class name:

1
2
package {
3
    public class TestImplementation implements ITest {
4
        public function TestImplementation() {
5
        }
6
    }
7
}

With this addition, we have now declared that our class TestImplementation will implement the ITest interface. But all we've done is declare that intention. We need to actually do the implementing. Add the test method declared by ITest to our TestImplementation class:

1
2
package {
3
    public class TestImplementation implements ITest {
4
        public function TestImplementation() {
5
        }
6
7
        public function test():void {
8
            trace("Just a little test.");
9
        }
10
    }
11
}

At this point, our class now fully implements the ITest interface. Our next step is to see this class in action.


Step 8: Testing the Interface and Implementation

Create an ActionScript 3.0 Flash file (File > New > ActionScript 3.0), and save it as "first-interface.fla" in your project folder.

Create one more text file, called "Main.as", in your project folder. This will be our document class. Before writing the contents of the class, hop back to your Flash file and enter "Main" as the Document Class in the Properties panel for the document.

Now, in Main.as, enter the following:

1
2
package {
3
    import flash.display.Sprite;
4
5
    public class Main extends Sprite {
6
        public function Main() {
7
            var tester:TestImplementation = new TestImplementation();
8
            tester.test();
9
        }
10
    }
11
}

This is an unsurprising usage of our TestImplementation class. We create a new instance of it, then call a method on it, resulting in a trace in the Output panel.

The result of running the test-interface FLAThe result of running the test-interface FLAThe result of running the test-interface FLA

This finished project is available in the download package in the "first-interface" folder.

Now, let's explore the relationship between interface and implementation.


Step 9: The Contract

When you write implements in your class file, it's like you're signing a contract with Flash, agreeing to fully implement each method defined in the interface file. When Flash sees that implements keyword, it will do quite a bit to ensure that you do, in fact, implement the interface.

Let's start with some modifications to the interface. Open up ITest.as and, first, let's add a method to it:

1
2
package {
3
    public interface ITest {
4
        function test():void;
5
        function anotherTest():void;
6
    }
7
}

When we make this change, we're saying that any implementations of this interface must define these two methods. But, before we do that, go ahead an test the movie right now. You should see that we get compiler errors:

1
2
[...]/TestImplementation.as, Line 2 1044: Interface method anotherTest in namespace ITest not implemented by class TestImplementation.

That error says, in rather stilted computer jargon, that we have failed to uphold the contract that we've entered. We said we'd implement the ITest interface, yet we never implemented the anotherTest method.

Let's try to fix that problem. Back in TestImplementation.as, add the method in question, but we'll deliberately make a mistake for illustrative purposes:

1
2
package {
3
    public class TestImplementation implements ITest {
4
        public function TestImplementation() {
5
        }
6
7
        public function test():void {
8
            trace("Just a little test.");
9
        }
10
11
        public function anotherTest(input:String):void {
12
            trace("Another test: " + input);
13
        }
14
    }
15
}

If you run the movie now, you'll get a different compiler error. That means that we've solved the previous error at least, but not to the satisfaction of the Flash compiler. It is now complaining that:

1
2
[...]/TestImplementation.as, Line 2 1144: Interface method anotherTest in namespace ITest is implemented with an incompatible signature in class TestImplementation.

This is due to the use of the input argument in our implementation of the anotherTest method. It's not declared in the interface, therefore we have an "incompatible signature." If you clean that up:

1
2
public function anotherTest():void {
3
    trace("Another test");
4
}

You'll see that we can now compile successfully. The lesson here is that interfaces are rather strict. Once you say you'll implement one, you're bound by that contract to implement each method exactly as declared by the interface. Not doing so means your SWF will not run due to compiler errors.


Step 10: The Limits of the Contract

Now, please be aware of an important aspect to this: an interface only defines a minimum of methods that must be present in your implementation class. That is, since our TestImplementation class implements ITest, we are required to at least have a test method and an anotherTest method defined. But the interface has no jurisdiction over what other methods may be declared. We can define as many other properties and methods as we like, just so long as the test and anotherTest methods are implemented.

To explore this, we'll add a third method to TestImplementation.as:

1
2
package {
3
    public class TestImplementation implements ITest {
4
        public function TestImplementation() {
5
        }
6
7
        public function test():void {
8
            trace("Just a little test.");
9
        }
10
11
        public function anotherTest():void {
12
            trace("Another test");
13
        }
14
15
        public function extraneousTest():void {
16
            trace("One more test.");
17
        }
18
    }
19
}

Again, this method is not defined in ITest. If you run the movie now, you'll see no change. Everything compiles just fine. Our implementation just happens to have an extra method on it.


Step 11: Interfaces as Datatypes

Now for something a little more tricksy. I mentioned before that the interface is, like a class, a datatype available in ActionScript. We can check this out by making a small change to the Main.as file:

1
2
public function Main() {
3
    var tester:ITest = new TestImplementation();
4
    tester.test();
5
}

The datatype of the tester variable was previously TestImplementation. It is now ITest. If you run the movie now, you'll find absolutely no change to the functionality. The datatype does have some subtle ramifications, though. Note that we can expand our usage of the object to involve another method call:

1
2
public function Main() {
3
    var tester:ITest = new TestImplementation();
4
    tester.test();
5
    tester.anotherTest();
6
}

And this will run as you expect it to.

The Output panel showing the two traces from the two method callsThe Output panel showing the two traces from the two method callsThe Output panel showing the two traces from the two method calls

However, adding a third method call like so:

1
2
public function Main() {
3
    var tester:ITest = new TestImplementation();
4
    tester.test();
5
    tester.anotherTest();
6
    tester.extraneousTest();
7
}

Will result in the following compiler error:

1
2
[...]/Main.as, Line 9   1061: Call to a possibly undefined method extraneousTest through a reference with static type ITest.

If you're an ActionScript ninja, you'll see what's going on here. The tester variable has an instance of TestImplementation stored in it. TestImplementation has an extraneousTest method defined in it. At first glance, we may thus assume that tester would be able to execute the extraneousTest method.

However, the tester variable has been datatyped as ITest (not as TestImplementation). ITest does not declare an extraneousTest method. The compiler, therefore, when evaluating the usage of the tester variable, looks at the ITest interface for allowable actions, not the TestImplementation class. So when the compiler encounters the call to extraneousTest, it realizes that extraneousTest was not defined in ITest and produces the above error.

What you should learn from this, aside from the mechanics of what just happened, is that when we datatype the variable as an interface, we should then subsequently treat it as the interface, regardless of what the actual concrete type the variable actually is.

Now, why would we do that? Read on.


Step 12: Polymorphism

If you're at all like me, you will at this point be comfortable with how interfaces work, how they can be created and used, and generally what they do. But you'll be at a loss for why in Dog's green earth you'd want to use one. Seems like a lot of extra work for no added features, right?

I have no problem admitting that it took me about a year of programming with OOP techniques before I finally realized the value of interfaces. I don't say this to scare you off. I say this to help you feel human. This stuff is hard. You are not stupid, you're just being introduced (as gently as I can possibly manage) to a rather advanced concept.

With that in mind, I need to bring up one of the tenets of Object-Oriented Programming: Polymorphism. You've been introduced to encapsulation and inheritance in the previous OOP tutorials. Polymorphism is another of these essential ingredients that makes OOP what it is.

Of course, we need to define the word. It's the strangest of all of the words we've encountered so far. It's from Greek: poly meaning "many" and morph meaning "form". "Many forms."

In terms of OOP, polymorphism is the ability of a variable to be strongly typed, yet be capable of handling several different datatypes. And, of course, interfaces place a key role in this.

Now, that probably doesn't make much sense just now. So let's move on to a more hands-on illustration, with a new, yet still simple, example.


Step 13: Getting Set Up For Magic

We'll create a whole new project for this illustration. You can follow along with the abbreviated sub-steps listed in this step, or you can simply open up the "language-start" project, found in the download package.

  1. Create a new project folder.
  2. Create a new AS3 FLA in that folder, named language.fla.
  3. Create a new document class for that FLA, named Main.as. This is the code to enter into it:

    1
    2
    package {
    
    3
        import flash.display.Sprite;
    
    4
        public class Main extends Sprite {
    
    5
            public function Main() {
    
    6
                var english:IPhrases = new EnglishPhrases();
    
    7
                trace(english.greeting());
    
    8
                trace(english.urgentSituation());
    
    9
            }
    
    10
        }
    
    11
    }
    
  4. Create a new interface file, called "IPhrases.as". This is the code for that file:

    1
    2
    package {
    
    3
        public interface IPhrases {
    
    4
            function greeting():String;
    
    5
            function urgentSituation():String;
    
    6
        }
    
    7
    }
    
  5. Create one more file, a class named "EnglishPhrases.as", and enter this code:

    1
    2
    package {
    
    3
        public class EnglishPhrases implements IPhrases {
    
    4
            public function EnglishPhrases() {
    
    5
            }
    
    6
            public function greeting():String {
    
    7
                return "Hello.";
    
    8
            }
    
    9
            public function urgentSituation():String {
    
    10
                return "Where is the bathroom?";
    
    11
            }
    
    12
        }
    
    13
    }
    

Go ahead and test it the movie, just to make sure everything is copacetic. You should get two traces in the Output panel:

A greeting and an urgent question in the Output panelA greeting and an urgent question in the Output panelA greeting and an urgent question in the Output panel

This project is very similar to our previous project. The major thing that has changed is the names of the files and methods involved. The classes and interface, and methods defined therein, have more meaningful names, which hopefully key you into what it is that they do.

That thing that they do is provide simple phrases in a given language. We are currently set up for English, but as you can guess, we'll be changing that in the next step.

Before moving on, I should note the other significant change is that our interface methods are declared as having to return Strings. This is a rather irrelevant change, but bears mentioning.

Lastly, other than the two changes, the project is conceptually identical to what we've done so far; we have an interface, and one implementation of that interface. A Flash file and its document class then set up a variable datatyped to the interface, and create an instance of the class into the variable. Two methods are then called on the instance.

Now, on with the magic.


Step 14: Implement the Interface, Again

Our goal is to add one more class. This class will also be an implementation of IPhrases. Both will coexist peacefully; no implosion of the universe or anything. Yet.

Create another text file in your project folder, called "SpanishPhrases.as". Add this code to the class:

1
2
package {
3
    public class SpanishPhrases implements IPhrases {
4
        public function SpanishPhrases() {
5
        }
6
        public function greeting():String {
7
            return "Hola.";
8
        }
9
        public function urgentSituation():String {
10
            return "¿Dónde está el baño?";
11
        }
12
    }
13
}

(Note that if you are using the file provided in the download package, it was saved with UTF-8 encoding. You may need to jigger your text editor if those non-ASCII characters (like ¿ and ñ) show up strangely)

As you can see, we enter into the contract by writing implements IPhrases. And then we proceed to actually implement the methods defined in IPhrases. Our implementation here is similar, yet unique from EnglishPhrases. So now we have two concrete implementations of IPhrases in our project. Let's try using them. In Main.as, add some code:

1
2
public function Main() {
3
    var english:IPhrases = new EnglishPhrases();
4
    trace(english.greeting());
5
    trace(english.urgentSituation());
6
7
    var spanish:IPhrases = new SpanishPhrases();
8
    trace(spanish.greeting());
9
    trace(spanish.urgentSituation());
10
}

Run the Flash movie, and you'll get double the Output:

Output panel showing result of all those tracesOutput panel showing result of all those tracesOutput panel showing result of all those traces

From the aspect of our Main class, pretty much all we can see is that we have two objects, both of them IPhrases objects. They happen to have the same interface. Yet they have differing implementations, so while the code used to run those two objects is very similar, the results are different.


Step 15: Polymorphism, Again

Now, to really illustrate polymorphism, we need to show that a single variable, typed as an interface, can hold any number of concrete implementation types.

We'll start by hard-coding the concrete types. Revert your Main.as file to this:

1
2
public function Main() {
3
    var phrases:IPhrases = new EnglishPhrases();
4
    trace(phrases.greeting());
5
    trace(phrases.urgentSituation());
6
}

Run it, and the results should be predictable.

Now, change line 6 so that instead of creating a new EnglishPhrases instance, we create a SpanishPhrases instance:

1
2
public function Main() {
3
    var phrases:IPhrases = new SpanishPhrases();
4
    trace(phrases.greeting());
5
    trace(phrases.urgentSituation());
6
}

And run it again. The results should be expected, but what I want you to focus on is how the only thing we just changed was name of the class after the new keyword. The variable didn't change, most importantly the datatype of the variable. If we had used EnglishPhrases as the datatype, then the first example would work but the second one would not. The compiler would complain about trying to put an SpanishPhrases object into a variable typed as EnglishPhrases.

Hopefully this will illustrate not only the concept of polymorphism, but also that the implementation details can indeed be different, while the interface remains the same. You may also realize that encapsulation comes into play here, as well; the implementation details are encapsulated in the concrete classes, exposing just the interface.


Step 16: A Basic UI

Let's take this one step further, and create a basic UI that lets the user trigger greeting and urgentSituation from button clicks.

We could draw some buttons in Flash and work with the UI that way, but let's take the opportunity to capitalize on the reusability of classes and some of the work we've done before. If you've followed along with the OOP tutorials so far, you'll have created a Button101 class. If not, or if you followed along but didn't keep the work, you can find the class in the download package.

To use it in this project, we need to either copy the Button101.as file into our current project folder, or make sure the source paths are set up to point to the folder that houses the Button101.as file already. My previous tutorial on Object-Oriented Programming covered how to set up the source path, if you need a refresher. For simplicity right now, I'm just going to copy it into my current project folder.

With the Button101 class accessible to the language project, we can set up our UI with code in Main.as. We'll be sort of starting over with this class, so just replace the current contents with this:

1
2
package {
3
    import flash.display.Sprite;
4
    import flash.events.MouseEvent;
5
6
    public class Main extends Sprite {
7
8
        private var _phrases:IPhrases;
9
10
        public function Main() {
11
            _phrases = new EnglishPhrases();
12
13
            var test1Button:Button101 = new Button101();
14
            test1Button.label = "greeting()";
15
            test1Button.x = 10;
16
            test1Button.y = 10;
17
            addChild(test1Button);
18
            test1Button.addEventListener(MouseEvent.CLICK, doGreeting);
19
20
            var test2Button:Button101 = new Button101();
21
            test2Button.label = "urgentSituation()";
22
            test2Button.x = 10;
23
            test2Button.y = 100;
24
            addChild(test2Button);
25
            test2Button.addEventListener(MouseEvent.CLICK, doUrgentSituation);
26
27
        }
28
29
        private function doGreeting(e:MouseEvent):void {
30
            trace(_phrases.greeting());
31
        }
32
33
        private function doUrgentSituation(e:MouseEvent):void {
34
            trace(_phrases.urgentSituation());
35
        }
36
    }
37
}

First, note that we've created a property to hold an IPhrases object, and we create it as a EnglishPhrases object in the constructor. We're making a property so that this object persists and we can reference it when the user clicks the buttons.

Second, the bulk of the code is devoted to creating two buttons, which are hooked up to two click event listeners. Those listeners call one of two methods on the IPhrases object.

Go ahead and test it now; you'll see something like the following:

The SWF running in the IDEThe SWF running in the IDEThe SWF running in the IDE

And if you click the buttons, you'll get the traces.

The Output panel after clicking on the buttons a few timesThe Output panel after clicking on the buttons a few timesThe Output panel after clicking on the buttons a few times

This step wasn't anything exciting, this is just to get some stuff out of the way for the next step.


Step 17: ¿Habla Español?

Let's church it up a little more and add buttons to the UI that let us choose which language we want to use. Right now, we have the EnglishPhrases class as our _phrases object. But let's make things a little more flexible and dynamic.

We're going to add two more buttons in the UI. In Main.as, in the constructor, add the highlighted code:

1
2
public function Main() {
3
    _phrases = new EnglishPhrases();
4
5
    var test1Button:Button101 = new Button101();
6
    test1Button.label = "greeting()";
7
    test1Button.x = 10;
8
    test1Button.y = 10;
9
    addChild(test1Button);
10
    test1Button.addEventListener(MouseEvent.CLICK, doGreeting);
11
12
    var test2Button:Button101 = new Button101();
13
    test2Button.label = "urgentSituation()";
14
    test2Button.x = 10;
15
    test2Button.y = 100;
16
    addChild(test2Button);
17
    test2Button.addEventListener(MouseEvent.CLICK, doUrgentSituation);
18
19
    var englishButton:Button101 = new Button101();
20
    englishButton.label = "English";
21
    englishButton.x = 300;
22
    englishButton.y = 10;
23
    addChild(englishButton);
24
    englishButton.addEventListener(MouseEvent.CLICK, useEnglish);
25
26
    var spanishButton:Button101 = new Button101();
27
    spanishButton.label = "Español";
28
    spanishButton.x = 300;
29
    spanishButton.y = 100;
30
    addChild(spanishButton);
31
    spanishButton.addEventListener(MouseEvent.CLICK, useSpanish);
32
}

Now, we need to write the two new event handlers. After the doUrgentSituation method, add these methods:

1
2
private function useEnglish(e:MouseEvent):void {
3
    _phrases = new EnglishPhrases();
4
}
5
6
private function useSpanish(e:MouseEvent):void {
7
    _phrases = new SpanishPhrases();
8
}

And try it out. You'll see something like this:

The four-button UIThe four-button UIThe four-button UI

Things start out in English, and clicking the original two buttons will be the same as before. But try clicking the Spanish button, and then on the "greeting()" and "urgentSituation()" buttons. Notice anything different?

The Output panel after clicking on the Spanish buttonThe Output panel after clicking on the Spanish buttonThe Output panel after clicking on the Spanish button

Step 18: Why Is The Interface Approach Better?

Let's take a moment to talk about some theory. You may still be wondering why we went to the trouble of creating the interface, creating the implementations, and setting up the UI logic to execute such a simple task. After all this would have accomplished the same end result, all in the Main class (with the help of the Button101 class):

1
2
public class Main extends Sprite {
3
4
    private var _language:String;
5
6
    public function Main() {
7
        _language = "en";
8
9
        var test1Button:Button101 = new Button101();
10
        // Set up the rest of the buttons as before...

11
    }
12
13
    private function doGreeting(e:MouseEvent):void {
14
        switch (_language) {
15
            case "en":
16
                trace("Hello.");
17
                break;
18
            case "es":
19
                trace("Hola.");
20
                break;
21
        }
22
    }
23
24
    private function doUrgentSituation(e:MouseEvent):void {
25
        switch (_language) {
26
            case "en":
27
                trace("Where is the bathroom?");
28
                break;
29
            case "es":
30
                trace("¿Dónde está el baño?");
31
                break;
32
        }
33
    }
34
35
    private function useEnglish(e:MouseEvent):void {
36
        _language = "en";
37
    }
38
39
    private function useSpanish(e:MouseEvent):void {
40
        _language = "es";
41
    }
42
}

Yes, that would have had the exact same result as what we did in the previous step. So why bother with the two extra classes and the interface?

Well, partly because this is a tutorial, and I need to ask you to trust me and follow along, understanding that this particular example a little on the trivial side, and that going to such lengths might be overkill in the real world. This example is simplified in order to avoid the distraction of other application mechanics, so we can focus on the topic at hand.

But let's put that aside, and consider why you'd want to look closer at interfaces for some programming tasks.

The most compelling reason in my mind is type safety. Consider what it would take to expand the linguistic capabilities of this little program. To add a language, you would have to go into each switch statement where the localized string is determined and add a case, taking care to spell the value of the _language property is accurate. To add a string, you'd have to add a new switch statement, ensuring that all existing languages are accounted for (and again, spelled accurately).

Now imagine that this is a distilled version of a larger website or application that was localized according to region. Maybe the application support 15 different languages, along with several dozen individual strings that need localized per language. The trouble introduced in adding strings or languages gets magnified as the application increases in scope.

And now consider the same task of adding strings and languages to the original, interface-driven version of the program. To add a new string, you would add the method to the ITest interface, and then implement the new method in all existing concrete objects. To add a new language, you simply need to create a new concrete implementation of IPhrases.

The amount of work involved may not be any less than with the other approach, but you are all but guaranteed that your updated application will work once it compiles successfully. The interface won't let you compile if you forget to add the new string to a concrete language object, or if you don't fully implement the interface with your new language object.

If that didn't convince you, I have more reasons. Our rewrite places all of the logic in a single class. This mixes logic for setting up the UI with logic for delivering localized strings in one class. By delegating responsibilities to other classes we get a cleaner idea of what each class is in charge. Focused classes tend to be less error-prone.

Not only that, but creating more, smaller classes lets us more easily share that responsibility to other objects; if we need the "greeting" string somewhere other than in Main, it would be easy provide access to the IPhrases objects and know that the logic is sound.

I could go on, but you're probably feeling a little overwhelmed as it is. If you need it, take a little breather, and/or review the previous exercise and explanation. We're officially done with the new concepts for this tutorial, and we'll be rounding it out with two more examples of interfaces in action.


Step 19: One More Example

We will again start with a new project. And as with the language example, you can either open the "debugger-start" project (in the download package) or follow these condensed steps:

  1. Create a new folder for the project, named debugger.
  2. Create an AS3 FLA file, and save it in the project folder as debugger.fla.
  3. Create a document class for the FLA and save it as Main.as. Make sure the FLA is set up to use Main as the document class. Add this code to the file:

    1
    2
    package {
    
    3
        import flash.display.*;
    
    4
        import flash.events.*;
    
    5
        import flash.system.*;
    
    6
        public class Main extends Sprite {
    
    7
            public function Main() {
    
    8
                // Fill this in later.
    
    
    9
                var message1Button:Button101 = new Button101();
    
    10
                message1Button.label = "Message 1";
    
    11
                message1Button.x = 10;
    
    12
                message1Button.y = 10;
    
    13
                addChild(message1Button);
    
    14
                message1Button.addEventListener(MouseEvent.CLICK, message1);
    
    15
    			
    
    16
    			var message2Button:Button101 = new Button101();
    
    17
    			message2Button.label = "Message 2";
    
    18
    			message2Button.x = 10;
    
    19
    			message2Button.y = 100;
    
    20
    			addChild(message2Button);
    
    21
    			message2Button.addEventListener(MouseEvent.CLICK, message2);
    
    22
    		}
    
    23
    		private function message1(e:MouseEvent):void {
    
    24
    			// Fill this in later.
    
    
    25
    		}
    
    26
    		private function message2(e:MouseEvent):void {
    
    27
    			// Fill this in later.
    
    
    28
    		}
    
    29
    	}
    
    30
    }
    
  4. Create an interface file, saved as "IDebugger.as" in the project folder. This is the code:

    1
    2
    package {
    
    3
        public interface IDebugger {
    
    4
            function out(message:*):void;
    
    5
        }
    
    6
    }
    
  5. Create a class file named "Trace.as" and save it in the project folder with the following code (to be expanded on in a moment):

    1
    2
    package {
    
    3
        public class Trace implements IDebugger {
    
    4
            public function out(message:*):void {
    
    5
                // Fill this in later
    
    
    6
            }
    
    7
        }
    
    8
    }
    
  6. Create one more class file, save it as "Log.as" in the project folder, and add this code:

    1
    2
    package {
    
    3
        public class Log implements IDebugger {
    
    4
            public function out(message:*):void {
    
    5
                // Fill this in later
    
    
    6
            }
    
    7
        }
    
    8
    }
    

There is nothing new here, but here's a quick run-down. The Main class creates two buttons and hooks them up to two event listeners that we'll be filling in next. The IDebugger interface defines a single method: out. The idea is that this will take a message as the sole parameter and somehow display it. Finally, we have two implementations of the interface, Trace and Log, both of which are merely stubbed out right now.

Let's work out those implementations right now. In Trace.as, remove the comment in the out method and replace it with this logic:

1
2
public function out(message:*):void {
3
    trace(message);
4
}

Doesn't seem like it's doing much, simply wrapping around the built in trace function. And that's about it. But let's complete the other implementation. Open up Log.as and flesh it out a bit:

1
2
package {
3
    import flash.external.*;
4
    public class Log implements IDebugger {
5
        public function out(message:*):void {
6
            ExternalInterface.call("console.log", message.toString());
7
        }
8
    }
9
}

Now things get interesting. After importing the external package, we can use ExternalInterface to call the JavaScript function console.log. Instead of using trace, we can use the JavaScript console built into most modern browsers (note that I'm not bothering to check for the availability of ExternalInterface or console.log; I'm going light on error-checking for the sake of more convenient illustrations).

Now back to Main.as, where we can use these classes. First, add a _debugger property to the class, typed as an IDebugger.

1
2
public class Main extends Sprite {
3
    private var _debugger:IDebugger;
4
    // Class continues...

And in the constructor, we'll add some logic to detect where the Flash Player is running, and based on that information, instantiate one concrete class or the other.

1
2
public function Main() {
3
    if (Capabilities.playerType == "ActiveX" || Capabilities.playerType == "PlugIn") {
4
        _debugger = new Log();
5
    } else {
6
        _debugger = new Trace();
7
    }
8
    // Constructor continues...

This logic uses the Capabilities class to get the player type, which will one of a handful of String values. If that value is either "ActiveX" or "PlugIn", then the Flash Player is running as a browser plug in (specifically, in Internet Explore if the value is "ActiveX", or any of the other browsers if the value is "PlugIn"). So, if we're in a browser, we create _debugger as a Log object, to use the JavaScript console. Otherwise, we're probably running in the Flash IDE (although AIR or as a projector are still options) and so we can use the Trace object.

Finally, let's fill in the two event handler methods, message1 and message2.

1
2
private function message1(e:MouseEvent):void {
3
    _debugger.out("Hello.")
4
}
5
private function message2(e:MouseEvent):void {
6
    _debugger.out("Where is the bathroom?")
7
}

Normally, you'd simply use trace to get some text to show up, but instead we use the _debugger object. Depending on how it was set up initially, you'll get traces to the Output panel, or logs to the JavaScript console.

So we've created an interface to getting messages from our ActionScript to some other place where we can see them. Again, the interface itself doesn't care how that happens, it just defines that it can happen. And from the Main class's perspective, it's using the interface as a datatype, so it, too, is agnostic as to how the messages get displayed, it's just using the _debugger property to send messages somewhere.

The two implementation classes are the ones that do the work of displaying messages, encapsulated away behind the interface. Thanks to our initial choice of an implementation based on our player type, we can see messages pretty easily no matter where the SWF is running.

To test this, you'll need to run the SWF once in the IDE and once in the browser. Start by just running the movie in Flash and clicking the buttons — you'll see the traces.

The messages in the Output panelThe messages in the Output panelThe messages in the Output panel

To test the second part of this, you'll need the SWF running in a browser. This, unfortunately, is not as simple. You will need an HTML page to house the SWF (create your own or use Flash's publishing templates) or else ExternalInterface won't work. Then you'll need to either put the HTML and SWF files on a server and browse to the page so that it's being served over HTTP (localhost should be fine), or you'll need to adjust the Flash Player security settings to let a locally-run SWF access the HTML page.

However you get that happening, open the console (found in Firebug if you're using Firefox, or the Web Inspector in Safari, Chrome, and other WebKit browsers, or under Developer Tool in the latest Internet Explorer), load the HTML page, and start clicking the buttons.

The messages in Safari's JavaScript ConsoleThe messages in Safari's JavaScript ConsoleThe messages in Safari's JavaScript Console

(And yes, I'm aware of the Flash Debug Player's ability to send trace messages to a log file, for viewing traces in a browser. This example is, well, an example. It's useful, but probably not as useful as the debug player trick — go here for more information — although you could argue that if a non-Flash developer needs to see some of your traces, it might be easier to use the console than to install and set up the debug player)

Now imagine that you can create further implementations. Perhaps one implementation creates a TextField object on the stage and simply spits messages out right in the SWF. A more advanced one could use a LocalConnection object to connect to an AIR app or a socket server to connect to any number of other pieces of software (even your phone). You could even take the JavaScript idea further and display messages in the browser but beyond the console.

This idea hopefully does two things. For one, I hope you might be sparked into some interesting debugging options. And for another, I hope this makes the idea of polymorphism a little more practical.


Step 20: Advanced Interface Techniques

We will attempt to make polymorphism even more practical as we work through the project that is the focus of the next tutorial, but we are nearing the end of this tutorial. I would like to mention a few extra capabilities of interfaces that I've not had an opportunity to bring up yet. I'll simply mention these aspects, and leave them as notifications that these capabilities exist, without going full-bore into explanations. I have, however, included working (if simple) examples of these techniques in the download package, all contained within the "advanced-interface-techniques" folder.

First, I want to mention the ability to implement multiple interfaces in a class. You can extend only one other class, but you can implement as many as you like or need to. To do this, you simply write implements and then list the interfaces you want to use, separated by a comma. For example:

1
2
package {
3
    public class MultiImplementation implements IOne, ITwo, IThree {
4
        //...

5
    }
6
}

This enters you into three implementation contracts, which you then need to fulfill.

Second, an interface can extend another interface. This works much like class extension does, only with interfaces. So if we have this interface:

1
2
package {
3
    public interface ISuper {
4
        function basicMethod():void;
5
    }
6
}

We can create another interface which extends ISuper like this:

1
2
package {
3
    public interface ISub extends ISuper {
4
        function fancyMethod():void;
5
    }
6
}

Extending an interface means that the subinterface inherits the method declaration of the superinterface, and can add more declarations to the mix. So in the above example, ISub may only define a single method in the file, but really requires two methods to be implemented: basicMethod and fancyMethod. For example:

1
2
package {
3
    public class SubImplementation implements ISub {
4
        public funciton basicMethod():void {
5
            trace("basic method");
6
        }
7
        public funciton fancyMethod():void {
8
            trace("fancy method");
9
        }
10
    }
11
}

This would be a successful implementation. Leaving out basicMethod would not.

Lastly, I want to briefly mention that you can certainly create a class that is both a subclass and an implementation. You simply need to list the superclass first, then the interface(s) next. For example:

1
2
package {
3
    import flash.display.Sprite;
4
    public class FancySprite extends Sprite implements IFancy {
5
        // ...

6
    }
7
}

This also works with multiple interfaces, although the rules of only one super class still apply.

As I said, I don't want to dwell on the techniques, as they're on the advanced side of an already advanced concept, but I wanted to mention them because they are useful, and may not be readily discoverable. I hope that by bringing them up here, you may retain a kernel of useful knowledge for when the time is right.


Step 21: Using Interfaces to De-Couple SWFs

I'd like to round things out with a final simple example. This particular technique is somewhat unique to Flash, given the ability of a SWF to load other SWFs, and the fact that web delivery encourages this modular approach to building websites so as to spread out the load.

Let's set up the problem first. We'll work with two Flash files, each with their own document class. Flash file "Main.fla" will load in Flash file "Loadee.fla." Our initial take at this looks like the following:

  1. Create a project folder for all of the files you'll be creating.
  2. Create two Flash files, one called Main.fla and the other called Loadee.fla.
  3. Create document classes for both of them, called Main.as and Loadee.as.
  4. The Flash files can remain empty, but be sure to set the document class for each.
  5. Write some code for the document classes.

The Loadee.as class will look like this (note that part of the point of this Flash piece in this exercise is to provide KBs worth of code. The following listing is excerpted. To get the whole _pixels Array, please look in the download package for a folder called crosstalk-start for this class):

1
2
package {
3
4
    import flash.display.*;
5
    import com.greensock.*;
6
    import com.greensock.easing.*;
7
8
    public class Loadee extends Sprite {
9
10
        private var _bmd:BitmapData;
11
        private var _bmp:Bitmap;
12
13
        public function Loadee() {
14
            _bmd = new BitmapData(_pixels[0].length, _pixels.length, false, 0xFF0000);
15
            _bmp = new Bitmap(_bmd);
16
            addChild(_bmp);
17
18
            var iLen:uint = _pixels.length;
19
            for (var i:uint = 0; i < iLen; i++) {
20
                var jLen:uint = _pixels[i].length;
21
                for (var j:uint = 0; j < jLen; j++) {
22
                    _bmd.setPixel(j, i, _pixels[i][j]);
23
                }
24
            }
25
26
            _bmp.alpha = 0;
27
            display();
28
        }
29
30
        public function display():void {
31
            TweenLite.to(_bmp, 0.5, {alpha:1});
32
        }
33
34
        private var _pixels:Array = [
35
            [0xc95016, 0xcc5316, 0xcc5716, 0xcf5716, 0xcf5716, 0xcf5716, 0xcf5a19,
36
            0xcf5a19, 0xcf5a19, 0xcf5a19, 0xcf5d19, 0xcf5d19, 0xcf5d19, 0xcf6019,
37
            0xcf6019, 0xcf6019, 0xd26019, 0xd2601d, 0xd2641d, 0xd2641d, 0xd2641d,
38
            0xd2671d, 0xd2671d, 0xd2671d, 0xd2671d, 0xd26a1d, 0xd26a1d, 0xd26a1d,
39
            0xd26d1d, 0xd66d20, 0xd66d20, 0xd66d20, 0xd67020, 0xd67020, 0xd67020,
40
            0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020, 0xd67020,
41
42
            // ...

43
            // To avoid an impossibly long scrolling page, this code is truncated.

44
            // Please look at the class Loadee.as in the folder "crosstalk-start"

45
            // for the complete code.

46
47
            ]
48
        ]
49
50
    }
51
}

I won't get into the logic of this class, suffice it to say that it programmatically draws a Bitmap using pixel values stored in a two-dimensional array. In other words, it's a bitmap image stored in a text file. When finished, you'll see the Activetuts+ logo appear. For now, the logo has an alpha of 0, so you won't see it. There is a display() method, though, which uses TweenLite to fade the logo in.

The Main.as class file is centered around loading the Loadee SWF:

1
2
package {
3
4
    import flash.display.*;
5
    import flash.events.*;
6
    import flash.net.*;
7
8
    public class Main extends Sprite {
9
10
        public function Main() {
11
            var loader:Loader = new Loader();
12
            loader.load(new URLRequest("Loadee.swf"));
13
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
14
            addChild(loader);
15
        }
16
17
        private function onLoadComplete(e:Event):void {
18
            var loadedSwf:Sprite = e.target.content as Sprite;
19
20
        }
21
22
    }
23
24
}
  1. Publish the "Loadee" movie
  2. Run (Test Movie) the "Main" movie.

You won't see anything but a blank canvas. The reason is that we're not calling the display method in the Loadee object. Now, here's where things get tricky.

Suppose we want to call display from the Main SWF. The reasons could be any of a number of valid reasons. Quite possibly we want to load the SWF but probably not display it right away, so we'll want to have control over when that moment of display happens. However, we want the Loadee SWF to be in control of its own display animation logic.

So if you write this code:

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:Sprite = e.target.content as Sprite;
4
    loadedSwf.display();
5
}

You'll get a compiler error, because the display method is not defined on Sprite, which is how the loadedSwf variable is typed.

1
2
decoupled-swfs-start/Main.as, Line 18   1061: Call to a possibly undefined method display through a reference with static type flash.display:Sprite.

So, you figure you can simply type the loadedSwf variable (and cast e.target.content) as a Loadee object.

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:Loadee = e.target.content as Loadee;
4
    loadedSwf.display();
5
}

And when you Test the Main movie, you'll see that it works:

The SWFs running in the IDEThe SWFs running in the IDEThe SWFs running in the IDE

That's great, but before you close down the Main movie, press Command-B (Control-B on the PC) to open up the Bandwidth Profiler (also available under View > Bandwidth Profiler while running a test movie). Note the size field. Mine says 13989 Bytes.

The Bandwidth Profiler showing the size of the SWFThe Bandwidth Profiler showing the size of the SWFThe Bandwidth Profiler showing the size of the SWF

Now, undo the recent changes. We'll reinstate the type as Sprite and remove the line that calls display():

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:Sprite = e.target.content as Sprite;
4
}

And test the movie again. The Bandwidth Profiler should still be open from last time, but if not, open it up again and note the size. Mine now says 1016 Bytes.

What happened? We added over 13,000 Bytes…or around 13 KB. How?

As soon as we typed the loadedSwf variable as a Loadee, we use the Loadee class in our Main class. Normally, that sort of thing isn't so big of a deal, as you need to incorporate many different classes into a single, final, SWF. But at this point, we negated the goal of loading the Loadee in separately, as almost all of the weight of that SWF is contained in the Loadee class, as well as TweenLite. By virtue of using the Loadee class, even as a simple datatype for a single variable (as cast), the Flash compiler then compiles the entire class into the SWF. And any other classes in use by that class.

At this point, our two SWFs are "coupled" or, as I call it, there is "crosstalk" between the two SWFs. Classes that should belong solely to one SWF are being compiled (probably inadvertently) into another.

Interfaces can solve this problem. We can retain proper datatyping but de-couple the two SWFs. Here's how.

Create a new interface file in the project folder, can call it ILoadee.as. Add this code:

1
2
package {
3
    public interface ILoadee {
4
        function display():void;
5
    }
6
}

Next, we want our Loadee class to implement this interface. Add that to the class declaration:

1
2
public class Loadee extends Sprite implements ILoadee {

Now be sure to publish the Loadee SWF. We've made a change to it, so it needs to be republished.

Finally, in Main.as, redo the things that we took out most recently. We'll call display on loadedSwf, but instead of using Loadee as the datatype, we'll use ILoadee:

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:ILoadee = e.target.content as ILoadee;
4
    loadedSwf.display();
5
}

If you test Main now, you'll find that it works.

The SWFs working again, with a low file size as indicated by the Bandwidth ProfilerThe SWFs working again, with a low file size as indicated by the Bandwidth ProfilerThe SWFs working again, with a low file size as indicated by the Bandwidth Profiler

And check the Bandwidth Profiler again, and you should see something more like the original size of Main. I have 1146 Bytes. So it increased a little bit (130 Bytes), which is due to the use of the interface. But this is nothing compared to the 13 KB increase when we used the class directly. And at the same time, we've retained type safety by datatyping our loadedSwf. If we tried misusing it, say by calling show instead of display, or trying to pass in arguments to display, then the compiler would produce errors and help us out of our predicament.

If you want to test which classes are actually being compiled under different scenarios, you can refer to my Quick Tip on import statements for techniques on how to see which classes are compiled into a SWF.

If you plan on using this technique (and I hope you do), you should also know about a tiny gotcha. Let me illustrate it: go back to Loadee.as and simply remove the implements ILoadee part (that is, we are no longer implementing the ILoadee interface, just extending Sprite).

1
2
public class Loadee extends Sprite {

Republish the Loadee SWF.

Now re-test the Main SWF. You'll see an error pop up, but not a compiler error. It's a run-time error, which will show up in the Output panel when running in the Flash IDE.

1
2
TypeError: Error #1009: Cannot access a property or method of a null object reference.
3
    at Main/onLoadComplete()

The problem is that when we cast e.target.content as ILoadee, and in fact the loaded content is not an ILoadee type, then we get null back. Thus, loadedSwf is null, and so when we call loadedSwf.display() on the next line, that produces error: you can't call a method on something doesn't exist.

So, to protect against this possibility, we should test for it. There are a few ways we can do this, and they depend a bit on what exactly your motive for using the interface is.

If you want to guarantee that every loaded SWF adheres to the same interface (not a bad idea), then we can modify the onLoadComplete method like so:

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:ILoadee = e.target.content as ILoadee;
4
    if (!loadedSwf) {
5
        throw new Error("The loaded SWF does not implement the ILoadee interface.")
6
    }
7
    loadedSwf.display();
8
}

This checks for the non-value state of loadedSwf, and if that's the case, stops the rest of the method by throwing an error. Now, we're still getting a runtime error, just like before, but in this case we're getting a much more helpful error. Try testing the Main SWF again, and you'll get the same results, just more helpful wording in the error message.

On the other hand, if you want to allow for SWFs to not implement the ILoadee interface, and handle them differently, then it's just a matter of some conditional logic, alongside casts. You can try this in your onLoadComplete method:

1
2
private function onLoadComplete(e:Event):void {
3
    var loadedSwf:Sprite = e.target.content as Sprite;
4
    if (loadedSwf is ILoadee) {
5
        (loadedSwf as ILoadee).display();
6
    } else {
7
        trace("Need to do something with the loadedSwf variable as a Sprite.");
8
    }
9
}

The point here is that you test for the condition that the loaded SWF implements the interface, and if so, use it, and if not, do something else. The is keyword test the thing on the left to see if it, well, is the thing on the right. That is, if loadedSwf officially implements the ILoadee interface, then we get true. If not, we get false.

So, if is is an ILoadee object, then we treat it as that object. The cast is still necessary, as the variable is officialy a Sprite. But we can be sure that the cast will be successful because we just tested the variable for that type.

The advantage of this technique is flexibility. You could incorporate more than one interface. Maybe some SWFs are ILoadee SWfs, and others are ILoadeeWithCallback SWFs, and some are plain Sprites. With a few branches in your logic, you could handle each one appropriately.

To sum up (this was an unusually long step, after all), to facilitate inter-SWF communication, you should employ this technique. This technique involves three parts (plus a fourth, optional one):

  1. Create an interface(s) for the SWF(s) to be loaded (it's better if it's a single interface for all SWFs…polymorphism!)
  2. Implement the interface in the document class of the SWF(s) to be loaded.
  3. In the SWF doing the loading, use the interface as the datatype for the loaded content.
  4. To be safe, test for a successful implementation of the loaded content.

You can find an example of the finished project in the "crosstalk-finish" folder in the download package.

For the record, I've never heard anyone else refer to this phenomenon as "crosstalk," but it's a rather apt word to describe what's going on. I just wanted you to know that, because I don't want you to draw blank stares when you identify this particular problem, then say something like "We need to eliminate the crosstalk" to another developer.


Conclusion

This wasn't a slouch of a tut, and if you made it here, you certainly aren't a slouch of a learner, either. But the fun is just beginning. There a sequel to this interface tutorial, to be released shortly. In it, we'll focus most our efforts on building a single project, in which interfaces will play a key role.

Thanks for reading, and hanging in there with me!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.