Published: November 15, 2009
by Richard G. Baldwin
|
ActionScript Programming Notes # 0112
|
This tutorial lesson is part of a continuing series of lessons dedicated to object-oriented programming (OOP) with ActionScript.
The three main characteristics of an object-oriented program
Object-oriented programs exhibit three main characteristics:
There are two different ways to implement polymorphism:
I explained encapsulation, inheritance, and polymorphism based on class inheritance in previous lessons. (See Baldwin's ActionScript programming website.)
I will explain and illustrate polymorphism based on interface inheritance in this lesson.
Several ways to create and launch ActionScript programs
There are several ways to create and launch programs written in the ActionScript programming language. Most of the lessons in this series will use Adobe Flex as the launch pad for the sample ActionScript programs.
An earlier lesson titled The Default Application Container provided information on how to get started programming with Adobe's Flex Builder 3. (See Baldwin's Flex programming website.) You should study that lesson before embarking on the lessons in this series.
Some understanding of Flex MXML will be required
I also recommend that you study all of the lessons on Baldwin's Flex programming website in parallel with your study of these ActionScript lessons. Eventually you will probably need to understand both ActionScript and Flex and the relationships that exist between them in order to become a successful ActionScript programmer.
Will emphasize ActionScript code
It is often possible to use either ActionScript code or Flex MXML code to achieve the same result. Insofar as this series of lessons is concerned, the emphasis will be on ActionScript code even in those cases where Flex MXML code may be a suitable alternative.
I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.
I recommend that you also study the other lessons in my extensive collection of online programming tutorials. You will find a consolidated index at www.DickBaldwin.com.
What is an ActionScript interface?
In an earlier lesson titled "Inheritance - The Big Picture", I told you that unlike C++,
"ActionScript 3 does not support multiple inheritance. Instead it supports a different mechanism called an interface that provides most of the benefits of multiple inheritance without most of the problems."
I promised to explain the ActionScript interface in a future lesson, and that time has come.
What does the documentation have to say?
According to About interfaces,
"Interfaces are a type of class that you design to act as an outline for your components. When you write an interface, you provide only the names of public methods rather than any implementation. For example, if you define two methods in an interface and then implement that interface, the implementing class must provide implementations of those two methods.
Interfaces in ActionScript can declare methods and properties only by using setter and getter methods; they cannot specify constants. The benefit of interfaces is that you can define a contract that all classes that implement that interface must follow. Also, if your class implements an interface, instances of that class can also be cast to that interface."
According to Interfaces,
"An interface is a collection of method declarations that allows unrelated objects to communicate with one another...
Interfaces are based on the distinction between a method's interface and its implementation. A method's interface includes all the information necessary to invoke that method, including the name of the method, all of its parameters, and its return type. A method's implementation includes not only the interface information, but also the executable statements that carry out the method's behavior. An interface definition contains only method interfaces, and any class that implements the interface is responsible for defining the method implementations...
Another way to describe an interface is to say that it defines a data type just as a class does. Accordingly, an interface can be used as a type annotation, just as a class can. As a data type, an interface can also be used with operators, such as the is and as operators, that require a data type. Unlike a class, however, an interface cannot be instantiated. This distinction has led many programmers to think of interfaces as abstract data types and classes as concrete data types."
According to this author...
An interface is like a class in which all of the methods are abstract, meaning that only their user interface is declared. Their behavior or implementation is not defined. (Recall from an earlier lesson that it is not possible to declare an abstract method in an ActionScript class. All methods must be defined as concrete methods in ActionScript classes.)
A form of multiple inheritance
In ActionScript, a class can inherit from (extend) only one other class. However, a class can inherit from (implement) any number of interfaces. Furthermore, each interface can extend any number of other interfaces.
Therefore, an ActionScript class can inherit any number of concrete methods from one superclass and can inherit any number of abstract methods from any number of interfaces.
Concrete methods are required
Any class that inherits an abstract method must provide a concrete definition for the method or the class cannot be compiled.
ActionScript classes cannot be declared abstract. Therefore, the abstract method cannot be passed down the class hierarchy for definition by a subclass as is the case in Java. The concrete version must be defined in the class in which it is inherited. This means that it must be defined in the class that implements the interface.
What is a concrete method?
As a minimum, a concrete method is a method signature followed by a pair of matching curly braces (possibly containing a return statement). Normally the body of the method is coded between the curly braces defining the behavior of the method.
If the return type is void, the matching curly braces may be empty. In that case, the concrete method exhibits no observable behavior. It returns immediately without doing anything when it is called.
If the return type is not void, there must be a return statement that returns a value of the correct type.
Do empty methods have any value?
It is not uncommon for a class to implement an interface for which some of the interface methods are of no interest with regard to an object of the new class. However, the implementing class must provide concrete definitions for all of the methods inherited from the interface.
In that case, it is common practice to simply define the uninteresting methods as empty methods in the new class. If they ever do get called, they will simply return immediately without doing anything.
The program named Interface01 that I will explain in this lesson is an update of the program named Polymorph02 that I explained in the earlier lesson titled "Polymorphism - The Big Picture" (see Baldwin's ActionScript programming website).
The earlier program illustrated runtime polymorphism based on class inheritance and the overriding of inherited concrete methods.
This program will illustrate runtime polymorphism based on interface inheritance and the concrete definition of inherited abstract methods.
The project file structure
The project file structure is shown in Figure 1.
Figure 1. Project file structure.
|
|
Six ActionScript files
This project consists of one MXML file named Interface01, three class files, and three interface files. The class files are named:
The interface files are named:
(The class and interface files are all ActionScript files so I omitted the file extension from the file names listed above.)
Interface can declare any number of methods
An interface can declare any number of methods including setter and getter methods. As you will see later, the interface named IArea declares two methods named area and id.
The interface named IVolume declares one method named volume. Likewise, the interface named ICircumference declares one method named circumference.
The rules of the road
Any number of classes can implement the same interface. This makes it possible to treat objects instantiated from different classes as the common interface type.
A class can implement any number of interfaces. In this program, however, the classes named MyRectangle and MyCircle each implement only one interface named IArea.
Can extend any number of interfaces
An interface can extend any number of other interfaces. In this program, the IArea interface extends both the IVolume interface and the ICircumference interface.
Implementing the IArea interface causes both the MyRectangle class and the MyCircle class to inherit the following abstract methods:
The first two methods are inherited directly from the interface named IArea. The last two are inherited from the interfaces named ICircumference and IVolume by way of IArea.
Each of the two classes must provide concrete definitions for all four of the inherited abstract methods.
Program output at startup
Figure 2 shows the program output at startup.
Figure 2. Program output at startup.
|
|
The program GUI
The program GUI consists of one label, three buttons and a text area. When a button is clicked, a random process is used to instantiate an object of either the MyCircle class or the MyRectangle class. The new object's reference is saved in a variable of the interface type IArea. This is possible because both classes implement the common IArea interface.
Call methods using the reference of type IArea
Then the program calls the id method on the object whose reference is stored in the variable of type IArea. Depending on which button is clicked, the program then calls either the area method, the circumference method, or the volume method on the object.
Output after clicking the Area Button for circle
Clicking the button labeled Area, for example causes the output to change to something similar to either Figure 3 or Figure 4. An output similar to Figure 3 is produced if the random process instantiates an object of the MyCircle class.
Figure 3. Output after clicking the Area Button for circle.
|
|
Output after clicking the Area Button for rectangle
An output similar to Figure 4 is produced if the random process instantiates an object of the MyRectangle class.
Figure 4. Output after clicking the Area Button for rectangle.
|
|
Random radius, width, and height
I use the word similar because a random process is also used to establish the radius for the circle and to establish the width and height for the rectangle.
Both the MYCircle object and the MyRectangle object contain concrete versions of the four methods listed above. However, the behavior of those four methods in one object is different from the behavior of the four methods having the same names in the other object.
Polymorphism kicks in
The compiler can't possibly know which type of object will be instantiated as a result of the random process following each button click when the program is compiled. Therefore, the decision as to which set of methods to call as a result of each button click cannot be determined until runtime. This is runtime polymorphism.
The cardinal rule
The type of the object's reference determines which set of method names can be called on that reference. In this case, the set consists of the four methods declared in and inherited into the interface named IArea, which are shown in the above list.
The type of the object determines which method from the set of allowable names is actually called.
Run the program
You can run the program to see the outputs produced by repeatedly clicking each of the three buttons in Figure 2.
Will discuss in fragments
I will break the longer files in this application down and discuss them in fragments. Complete listings of all of the files are provided in Listing 10 through Listing 16 near the end of the lesson.
In keeping with my plan to emphasize ActionScript over Flex in this series of lessons, the MXML file for this application is very simple, instantiating only a single object of type Driver. A listing of the MXML file is provided in Listing 10 near the end of the lesson.
The class named Driver begins in Listing 1. A complete listing of the file is provided in Listing 11 near the end of the lesson.
Listing 1. Beginning of the class named Driver.
package CustomClasses{
import flash.events.*;
import mx.containers.HBox;
import mx.containers.VBox;
import mx.controls.Button;
import mx.controls.Label;
import mx.controls.TextArea;
public class Driver extends VBox{
private var textArea:TextArea = new TextArea();
private var myShape:IArea;
private var randomChoice:Number;
private var radius:uint;
private var rectWidth:uint;
private var rectHeight:uint;
|
Listing 1 declares several new instance variables. The most interesting variable is named myShape because it is declared to be of type IArea, which is the name of an interface.
References to objects of the classes MyCircle and MyRectangle will be stored in this variable.
Beginning of the constructor for the Driver class
The constructor for the Driver class begins in Listing 2.
Listing 2. Beginning of the constructor for the Driver class.
public function Driver(){//constructor
var label:Label = new Label();
label.text = "Interface Polymorphism Demo";
label.setStyle("fontSize",14);
label.setStyle("color",0xFFFF00);
addChild(label);
//Put three buttons in an HBox
var hbox:HBox = new HBox();
addChild(hbox);
var areaButton:Button = new Button();
areaButton.label = "Area";
hbox.addChild(areaButton);
var circButton:Button = new Button();
circButton.label = "Circumference";
hbox.addChild(circButton);
var volumeButton:Button = new Button();
volumeButton.label = "Volume";
hbox.addChild(volumeButton);
//Put the text area below the HBox.
textArea.width = 245;
textArea.height = 80;
addChild(textArea);
|
You shouldn't find any surprises in Listing 2. The code in Listing 2 simply constructs the GUI shown in Figure 2, placing the label, the buttons, and the text area in their respective locations.
The remainder of the constructor for the Driver class
The remainder of the constructor for the Driver class is shown in Listing 3.
Listing 3. The remainder of the constructor for the Driver class.
//Register a click event handler on each of the
// buttons
areaButton.addEventListener(
MouseEvent.CLICK,areaButtonHandler);
circButton.addEventListener(
MouseEvent.CLICK,circButtonHandler);
volumeButton.addEventListener(
MouseEvent.CLICK,volumeButtonHandler);
}//end constructor
|
Listing 3 registers a click event handler on each of the buttons shown in Figure 2.
The getRandomValues method
The method named getRandomValues is shown in Listing 4.
Listing 4. The getRandomValues method.
//Local utility method for getting and saving four
// random values.
private function getRandomValues():void{
randomChoice = Math.random();
radius = uint(10*Math.random() + 1);
rectWidth = uint(10*Math.random() + 1);
rectHeight = uint(10*Math.random() + 1);
}//end getRandomValues
|
This method is called by each of the event handler methods to get and save four random values that are subsequently used by the event handler. The random values are saved in the instance variables that are declared in Listing 1.
The event handler method named areaButtonHandler
The click event handler that is registered on the button labeled Area in Figure 2 is shown in Listing 5.
Listing 5. The event handler method named areaButtonHandler.
//Define click event handler methods.
private function areaButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() + myShape.area();
}//end areaButtonHandler
|
Decide between two classes
The code in Listing 5 uses the random value stored in randomChoice to decide whether to instantiate an object of the class named MyCircle or the class named MyRectangle.
Store object's reference as type IArea
The object's reference is stored in the instance variable named myShape, which is type IArea. Storage of the reference in a variable of that type is possible only because both objects implement the interface named IArea.
Call the id method using the reference
Then the code in Listing 5 uses the reference stored in myShape to call the id method and the area method on the object. The returned values are used to construct a new text string for the text area shown in Figure 3.
How is this possible?
Calling these methods using the reference of type IArea is possible only because abstract versions of both methods are inherited into both classes from the interface named IArea.
The other two click event handler methods
The click event handler methods that are registered on the Circumference button and the Volume button in Figure 2 are shown in Listing 6.
Listing 6. The other two click event handler methods.
private function circButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() +
myShape.circumference();
}//end circButtonHandler
private function volumeButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() + myShape.volume();
}//end circButtonHandler
}//end class
}//end package
|
The same methodology
The methodology behind these two event handlers is the same as the methodology behind the event handler method shown in Listing 5.
The main differences between the event handlers
The main differences are highlighted in yellow. In addition to using the reference of type IArea to call the id method on the objects, these two event handler methods call the circumference method or the volume method on the objects. Once again, this is possible only because the objects inherit those methods from the IArea interface.
The end of the Driver class
Listing 6 also signals the end of the class named Driver.
Since I mentioned the interface named IArea several times above, I will discuss it next.
The interface named IArea is shown in Listing 7.
Listing 7. The interface named IArea.
package CustomClasses{
public interface IArea extends IVolume,ICircumference{
function area():String;
function id():String;
}//end interface
}//end package
|
General information about an interface
The syntax for an interface looks a lot like the syntax for a class. However, the keyword class is replaced by the keyword interface.
An interface may contain only method declarations (with no bodies) and setter and getter method declarations. All method declarations are implicitly public, and the concrete definition of an interface method in a class must be declared public.
You cannot instantiate an object of an interface.
An interface cannot extend a class, but can extend any number of other interfaces.
IArea declares two methods and extends two interfaces
The interface shown in Listing 7 declares the abstract methods named area and id. It also extends the interfaces named IVolume and ICircumference.
Concrete method definitions are required
Any class that implements the interface named IArea must provide concrete definitions for the methods named id and area.
The class must also provide concrete definitions for any methods inherited into IArea from the interfaces that it extends. The class also inherits those methods by way of IArea.
The interface named IVolume is shown in its entirety in Listing 8.
Listing 8. The interface named IVolume.
package CustomClasses{
public interface IVolume{
function volume():String;
}//end interface
}//end package
|
This interface declares the method named volume. Because this interface is extended by the interface named IArea, any class that extends IArea inherits the abstract method named volume and must provide a concrete definition for the method.
The interface named ICircumference is very similar to the interface named IVolume. The code for this interface is provided in Listing 14 near the end of the lesson.
This interface declares the method named circumference. Once again, because this interface is extended by the interface named IArea, any class that extends IArea inherits the abstract method named circumference and must provide a concrete definition for the method.
The class named MyCircle is shown in its entirety in Listing 9
Listing 9. The class named MyCircle.
package CustomClasses{
public class MyCircle implements IArea{
private var radius:Number;
public function MyCircle(radius:Number){//constructor
this.radius = radius;
}//end constructor
public function area():String{
return "Radius = " + radius + "\n" +
"Area = " + Math.PI * radius * radius;
}//end area
public function id():String{
return "Circle\n";
}//end id
public function circumference():String{
return "Radius = " + radius + "\n" +
"Circumference = " + 2 * Math.PI * radius;
}//end function circumference
public function volume():String{
//Assumes that the shape is a cylinder with a
// depth of ten units.
return "Radius = " + radius + "\n" +
"Depth = 10\n" +
"Volume = " + 10 * Math.PI * radius * radius;
}//end area
}//end class
}//end package
|
Inherits four abstract methods
As you can see, this class implements the interface named IArea, causing it to inherit the following abstract methods:
The area and id methods are declared in the IArea interface and are inherited directly into the MyCircle class.
The circumference and volume methods are declared in the interfaces named ICircumference and IVolume, both of which are extended by the IArea interface. Therefore, the MyCircle class inherits those abstract methods by way the IArea interface.
Concrete definitions for four methods
Listing 9 provides concrete versions of the four inherited abstract methods. As a practical matter, this amounts to overriding inherited abstract methods. Note however, that unlike the case of overriding a concrete method inherited from a class, the keyword override is not required when overriding an abstract method inherited from an interface.
Other than the fact that this class implements an interface and overrides inherited abstract methods, there is nothing in Listing 9 that should be new to you.
The class named MyRectangle is defined in Listing 16 near the end of the lesson. This class is very similar to the MyCircle class shown in Listing 9. As with the MyCircle class, it implements the IArea interface. Therefore, it inherits and overrides the same four abstract methods shown in the above list.
I encourage you to run this program from the web. Then copy the code from Listing 10 through Listing 16. Use that code to create a Flex project. Compile and run the project. Experiment with the code, making changes, and observing the results of your changes. Make certain that you can explain why your changes behave as they do.
In this lesson, you gained an understanding and learned how to use runtime polymorphism based on the ActionScript interface.
Listing 10. Listing for the file named Interface01.mxml.
<?xml version="1.0" encoding="utf-8"?> <!--Illustrates polymorphism using an interface.--> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:cc="CustomClasses.*"> <cc:Driver/> </mx:Application> |
Listing 11. Listing for the file named Driver.as.
package CustomClasses{
import flash.events.*;
import mx.containers.HBox;
import mx.containers.VBox;
import mx.controls.Button;
import mx.controls.Label;
import mx.controls.TextArea;
public class Driver extends VBox{
private var textArea:TextArea = new TextArea();
private var myShape:IArea;
private var randomChoice:Number;
private var radius:uint;
private var rectWidth:uint;
private var rectHeight:uint;
public function Driver(){//constructor
var label:Label = new Label();
label.text = "Interface Polymorphism Demo";
label.setStyle("fontSize",14);
label.setStyle("color",0xFFFF00);
addChild(label);
//Put three buttons in an HBox
var hbox:HBox = new HBox();
addChild(hbox);
var areaButton:Button = new Button();
areaButton.label = "Area";
hbox.addChild(areaButton);
var circButton:Button = new Button();
circButton.label = "Circumference";
hbox.addChild(circButton);
var volumeButton:Button = new Button();
volumeButton.label = "Volume";
hbox.addChild(volumeButton);
//Put the text area below the HBox.
textArea.width = 245;
textArea.height = 80;
addChild(textArea);
//Register a click event handler on each of the
// buttons
areaButton.addEventListener(
MouseEvent.CLICK,areaButtonHandler);
circButton.addEventListener(
MouseEvent.CLICK,circButtonHandler);
volumeButton.addEventListener(
MouseEvent.CLICK,volumeButtonHandler);
}//end constructor
//Local utility method for getting and saving four
// random values.
private function getRandomValues():void{
randomChoice = Math.random();
radius = uint(10*Math.random() + 1);
rectWidth = uint(10*Math.random() + 1);
rectHeight = uint(10*Math.random() + 1);
}//end getRandomValues
//Define click event handler methods.
private function areaButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() + myShape.area();
}//end areaButtonHandler
private function circButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() +
myShape.circumference();
}//end circButtonHandler
private function volumeButtonHandler(
event:MouseEvent):void{
getRandomValues();
if(randomChoice < 0.5){
myShape = new MyCircle(radius);
}else{
myShape = new MyRectangle(rectWidth,rectHeight);
}//end else
textArea.text = myShape.id() + myShape.volume();
}//end circButtonHandler
}//end class
}//end package
|
Listing 12. Listing for the file named IArea.as.
package CustomClasses{
public interface IArea extends IVolume,ICircumference{
function area():String;
function id():String;
}//end interface
}//end package
|
Listing 13. Listing for the file named IVolume.as.
package CustomClasses{
public interface IVolume{
function volume():String;
}//end interface
}//end package
|
Listing 14. Listing for the file named ICircumference.as.
package CustomClasses{
public interface ICircumference{
function circumference():String;
}//end interface
}//end package
|
Listing 15. Listing for the file named MyCircle.as.
package CustomClasses{
public class MyCircle implements IArea{
private var radius:Number;
public function MyCircle(radius:Number){//constructor
this.radius = radius;
}//end constructor
public function area():String{
return "Radius = " + radius + "\n" +
"Area = " + Math.PI * radius * radius;
}//end area
public function id():String{
return "Circle\n";
}//end id
public function circumference():String{
return "Radius = " + radius + "\n" +
"Circumference = " + 2 * Math.PI * radius;
}//end function circumference
public function volume():String{
//Assumes that the shape is a cylinder with a
// depth of ten units.
return "Radius = " + radius + "\n" +
"Depth = 10\n" +
"Volume = " + 10 * Math.PI * radius * radius;
}//end area
}//end class
}//end package
|
Listing 16. Listing for the file named MyRectangle.as.
package CustomClasses{
public class MyRectangle implements IArea{
private var width:Number;
private var height:Number;
public function MyRectangle(
width:Number,height:Number){//constructor
this.width = width;
this.height = height;
}//end constructor
public function area():String{
return "Width = " + width + "\n" +
"Height = " + height + "\n" +
"Area = " + width * height;
}//end area
public function id():String{
return "Rectangle\n";
}//end id
public function circumference():String{
return "Width = " + width + "\n" +
"Height = " + height + "\n" +
"Circumference = " + 2 * (width + height);
}//end function circumference
public function volume():String{
//Assumes that the shape is a rectangular solid
// with a depth of ten units.
return "Width = " + width + "\n" +
"Height = " + height + "\n" +
"Depth = 10\n" +
"Volume = " + 10 * width * height;
}//end area
}//end class
}//end package
|
Copyright 2009, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor's degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-