An interface is nothing more than a list of public methods that must be present in any class that conforms to the interface.. Car class A public method also named useAccessory is added t
Trang 1The last important concept of object-oriented programming that we want to
discuss is polymorphism Although we’ll expand the explanation as this
sec-tion evolves, you can start by thinking of polymorphism as a design practice
that allows you to use objects of different types in a uniform manner For
example, for our vehicle exercise, you might create classes for land-, water-,
and air-based vehicles and write code to move each type of vehicle In this
scenario, it’s better to use one method name for moving all of these vehicle
types (such as “move”), instead of separate method names (like “drive,” “pilot,”
and “fly,” for moving a car, boat, and plane, respectively) Doing so makes your
code more flexible, more reusable, and easier to read and document
In ActionScript 3.0, polymorphism is commonly used with inheritance and/or
interfaces We’ll work with interfaces in a moment but, for now, think of them
as rulebooks for classes An interface is nothing more than a list of public
methods that must be present in any class that conforms to the interface For
example, you might continue to develop our vehicle exercise and eventually
end up with vehicles that contain public methods that activate (start up), go,
stop, and deactivate (shut down) your vehicles You can create an interface
that includes these method names and requires classes to adhere to that
inter-face This makes certain all of those classes can be controlled consistently
An interface doesn’t restrict your class to those methods, either Classes can
have their own public methods that are not in the interface, without
conse-quence As long as the interface methods are present, everyone will be happy
We’ll discuss the further role that interfaces play in polymorphism a little
bit later For now, let’s extend what you already know and show how to use
polymorphism with inheritance
Polymorphism and inheritance
Employing polymorphism with inheritance allows you to design subclasses
that can use the same method names as their superclasses, but without
creating a conflict For example, a superclass can have a public method
called “turn,” which multiple subclasses use One subclass, however, might
also have a public method called “turn,” that is either entirely different or
enhanced Ordinarily, the fact that a subclass inherits public methods from a
superclass means that the subclass would effectively have two methods called
“turn” and a conflict would exist
However, polymorphism allows the subclass method to replace or augment
the superclass method of the same name by overriding it Overriding a
meth-od tells the compiler that the new version of the methmeth-od (in the subclass)
takes precedence over the previous version of the method (in the superclass)
To demonstrate this process, let’s begin by adding two methods to our
Vehicle class If you want to look at the source files, they are in the
polymor-phism folder of the chapter archive The new methods can be seen in lines
N OT E
Only public and protected methods can
be seen by ActionScript 3.0 subclasses,
so they are the only kinds of methods that can be overridden.
Trang 233 through 39 in the following excerpt, and are named useAccessory() and
changeGear() Both of the new methods are available to the Car and Truck
subclasses through inheritance and notice that the functionality of the use-Accessory() method is to turn on a vehicle’s lights
Vehicle class
20 private function onLoop(evt:Event):void {
21 if (_moving) {
22 _fuelAvailable ;
23 _milesTraveled += _gasMileage;
24 if (_fuelAvailable < 1) {
25 this.removeEventListener(Event.ENTER_FRAME,
26 onLoop, false, 0, true);
27 }
28 trace(this, _milesTraveled, _fuelAvailable);
29 this.x = _milesTraveled;
30 }
31 }
32
33 public function changeGear(): void {
34 trace ( this , "changed gear");
35 }
36
37 public function useAccessory(): void {
38 trace ( this , "vehicle lights turned on");
39 }
Next let’s see how to override the useAccessory() method in the Car and
Truck classes so we can customize its functionality without having to change our API
Car class
A public method also named useAccessory() is added to the Car class, seen in lines 17 through 19 of the following excerpt Remember that this would ordi-narily conflict with the method of the same name in the Vehicle superclass, because of inheritance As discussed previously, we avoid this by preceding the method declaration, including its access control modifier, with the over-ride keyword
The functionality of the method is the same in both classes: to use an acces-sory So the useAccessory() method in the Car class can call its existing
openSunroof() method
13 public function openSunroof():void {
14 trace(this, "opened sunroof" );
15 }
16
17 override public function useAccessory(): void {
18 openSunroof();
19 }
The beauty of this arrangement is that you’ve created an API that employs
Trang 3without having to change the rest of your application For example, you might
have many method calls using the syntax useAccessory() that all open car
sunroofs If you later decide to change the accessory to something else, you
would need to edit only the Car class, not the many existing method calls, to
update your application
Truck class
Now we’ll do the same thing with the Truck class, but with a twist In some
cases when overriding, you may not want to entirely replace the original
behavior that exists in the superclass When needed, you can execute the
cus-tom code in the subclass method and call the same method in the superclass
To do this, add an instruction in the subclass method to explicitly call the
original superclass method, as seen in line 18 of the Truck class In this case,
you can’t simply use the super() statement the way you did earlier, because
that only works in the constructor Within a method, you must reference the
superclass using the super object, and follow it with the superclass method
you want to call The edit is in bold
13 public function lowerTailgate():void {
14 trace(this, "lowered tailgate" );
15 }
16
17 override public function useAccessory(): void {
18 super useAccessory();
19 lowerTailgate();
20 }
Tires class and Document class
No change to the Tires class is required, but we’ll make two changes to the
document class Main to show the outcome of your efforts First, in both Car
and Truck instances (compact and pickup), we’ll call the other method we
added, changeGear() (lines 18 and 25) This will show that the outcome of a
public method called from either car or truck will be the same if
polymor-phism is not in play
Next, we’ll follow the example discussed and change our code from calling
openSunroof() and lowerTailgate(), for compact and pickup respectively, to
both instances calling useAccessory() (lines 19 and 26) This will make our
code a bit more flexible, as we can later change the accessories in one or both
classes and not have to change our FLA to benefit from the adjustment
12 public function Main() {
13
14 compact = new Car(21, 18);
15 compact.x = 20;
16 compact.y = 20;
17 addChild(compact);
18 compact.changeGear();
19 compact.useAccessory();
20
Trang 421 pickup = new Truck(16, 23);
22 pickup.x = 20;
23 pickup.y = 100;
24 addChild(pickup);
25 pickup.changeGear();
26 pickup.useAccessory();
27
28 this.addEventListener(Event.ADDED_TO_STAGE,
29 onAddedToStage,
30 false, 0, true);
31 }
An abbreviated output follows As you can see, the car class traced its tires, the compact instance changed gear, and then used its accessory This opened
the sunroof, but nothing more because the Car class override replaced the functionality of the Vehicle useAccessory() method, which turned on the vehicle’s lights The pickup behaved similarly, but in addition to lowering its
tailgate, also turned on its lights This is because the Truck class also called the useAccessory() method in the superclass, rather than just overriding it [object Car] has high-performance radial tires
[object Car] changed gear [object Car] opened sunroof [object Truck] has storm-ready snow tires [object Truck] changed gear
[object Truck] lowered tailgate [object Truck] turned on lights [object Car] 21 17
[object Truck] 16 22 [object Car] 42 16 [object Truck] 32 21
Polymorphism and interfaces
Earlier, we said there’s another way to use polymorphism that doesn’t focus
on inheritance Because it’s not based on method overriding between sub-class and supersub-class, it’s applicable to more situations The general idea is the same, in that your coding is simplified by using the same method names across different object types However, it’s even more useful in that it adds additional flexibility by not requiring that you type your object to a specific class
To help explain this, let’s sideline a bit to revisit two important ActionScript 3.0 topics: compile-time error checking and the display list The benefit of using data typing with your objects is that the ActionScript compiler will warn you if you do something that’s incompatible with your stated data type
By design, the simplest case means that you can only work with one data type (A look at Chapter 2 will reinforce this idea if you need a quick review.) However, there are times when you may want things to be a bit more flexible For example, you may want to put either a MovieClip or Sprite into a vari-able If you type the variable as MovieClip, only a movie clip will be accepted
Trang 5from which both MovieClip and Sprite descend (see Chapter 4 for more
information), and the compiler won’t object
The downside to this is that it can be a bit too generic If, for example, you
used a movie clip method on an object that the compiler only understood as
a DisplayObject, an error would occur:
var thing:DisplayObject = new MovieClip();
thing.play();
Why? Because, although play() is a legal movie clip method, the compiler
doesn’t understand that thing is actually a movie clip It might be a sprite
(and that flexibility is the very reason we’re discussing this), and a sprite
doesn’t have a timeline
This can be addressed by casting (also discussed in Chapter 4), but that kind
of defeats the purpose of what we’re doing Instead, what if you could specify
a data type that was flexible enough to work with different kinds of objects,
but also knew which methods those objects supported? That’s where
inter-faces come in
As we explained earlier, an interface is simply a list of public methods that
must be present in a class The following is an example of an interface that
might be used with classes for devices that play music (like a radio or CD
player) All of the code for this discussion can be found in the polymorphism_
interface source code directory The interface is called IAudible and is found
in the IAudible.as source file It’s a very common practice to start the name of
all interfaces with a capital I, to differentiate them from classes
1 package {
2
3 public interface IAudible {
4
5 function turnOn():void;
6 function playSelection(preset:int):void;
7 function turnOff():void;
8
9 }
10 }
As you can see, not even the content of a method is included Only the name,
parameters and data types, and return data type (which are collectively called
the method’s signature) are included Also, any import statements needed to
support included data types are required (In this case, the compiler
auto-matically understands the int data type However, if a data type represents a
class, such as MovieClip or Event, that class must be imported.)
Once you’ve created an interface, you can require a class to adhere to it by
implementing it using the implements keyword in the interface declaration, as
shown in line 3 of the following simple Radio class (Radio.as):
1 package {
2
3 public class Radio implements IAudible {
4
Trang 65 public function Radio() {
6 trace( "radio added" );
7 }
8
9 public function turnOn():void {
10 trace( "radio on" );
11 }
12
13 public function playSelection(preset:int):void {
14 trace( "radio selection: channel" , preset);
15 }
16
17 public function turnOff():void {
18 trace( "radio off" );
19 }
20 }
21 } All this class does is trace appropriate diagnostic statements, identifying itself
as “radio” each time It complies with the interface because every method required is present Here is a CDPlayer class (CDPlayer.as) that also
imple-ments, and complies with, the same interface The purpose of the class is similar, but it identifies itself as “cd player” in each trace to demonstrate unique functionality
1 package {
2
3 public class CDPlayer implements IAudible {
4
5 public function CDPlayer() {
6 trace( "cd player added" );
7 }
8
9 public function turnOn():void {
10 trace( "cd player on" );
11 }
12
13 public function playSelection(preset:int):void {
14 trace( "cd player selection: track" , preset);
15 }
16
17 public function turnOff():void {
18 trace( "cd player off" );
19 }
20
21 public function eject():void {
22 trace( "cd player eject" );
23 }
24
25 }
26 } Although the Radio and CDPlayer classes do different things (demonstrated simply by the unique traces), the method names required by the interface are present in both classes This means that you can write a full application using a radio, later swap out the radio with a CD player, but not have to
Trang 7The CDPlayer class also demonstrates that additional methods, not referenced
by an interface, can appear in classes—as shown by the eject() method in
lines 21 through 23 An interface is only designed to enforce a contract with
a class, making sure the required methods are present It doesn’t restrict the
functionality of a class
Simple example
All that remains is putting this into practice The following basic
implemen-tation is found in the sound_system.fla source file The key step in using
interfaces in this context is typing to the interface If you type to Radio, you
can’t switch to CDPlayer later However, if you type to IAudible, the compiler
will nod approvingly at both Radio and CDPlayer Also, because the interface
rigidly enforces that all public methods are present, you don’t run into
situa-tions where the compiler is unsure if a method is legal This is polymorphism
at its best The following script starts with a radio and then switches to a CD
player, using methods in both cases without error
var soundSystem:IAudible = new Radio();
soundSystem.turnOn();
soundSystem = new CDPlayer();
soundSystem.turnOn();
soundSystem.playSelection(1);
Adding a sound system to your vehicles through composition
Now let’s practice what you’ve learned by composing the sound system
example into the ongoing vehicle exercise This will review encapsulation,
composition, and polymorphism
First, add another private property to the Vehicle class to hold the sound
system, just like we did when we composed Tires into the exercise It’s typed
to the interface to allow a vehicle to have any sound system that implements
IAudible The property can be seen in line 12 of the following excerpt from
the Vehicle.as source file:
8 private var _gasMileage:Number;
9 private var _fuelAvailable:Number;
10 private var _milesTraveled:Number = 0;
11 private var _moving:Boolean;
12 private var _soundSystem:IAudible;
Next, provide public access to this property by adding a getter and setter,
again typed to the IAudible interface The following excerpt, still in the
Vehicle.as source file, shows this addition in lines 64 through 70:
60 public function get milesTraveled():Number {
61 return _milesTraveled;
62 }
63
64 public function get soundSystem():IAudible {
65 return _soundSystem;
66 }
Trang 867
68 public function set soundSystem(device:IAudible): void {
69 _soundSystem = device;
70 }
The last class changes involve adding an instance of CDPlayer in the Car class, and a Radio instance in the Truck class—just as we did when adding Tires in the composition example This excerpt from the Car class (Car.as) shows the
change at the end of the constructor:
7 public function Car(mpg:Number, fuel:Number) {
8 super(mpg, fuel);
9 _tires = new Tires( "highperformance" );
10 trace(this, "has" , _tires.type, "tires" );
11 soundSystem = new CDPlayer();
12 } This excerpt from the Truck class (Truck.as) also adds the sound system at
the end of the constructor The edits in both classes appear in bold at line 11:
7 public function Truck(mpg:Number, fuel:Number) {
8 super(mpg, fuel);
9 _tires = new Tires( "snow" );
10 trace(this, "has" , _tires.type, "tires" );
11 soundSystem = new Radio();
12 } Finally, the document class is modified to use the sound system in both the
Car instance (compact) and Truck instance (pickup) when you click the stage
Shown in bold in the Main.as excerpt below, lines 42 through 44 access the
CD player and radio through the soundSystem property This triggers the get-ter method in the respective classes and returns the car’s CD player and the truck’s radio
39 public function onClick(evt:MouseEvent):void {
40 compact.go();
41 pickup.go();
42 compact.soundSystem.turnOn();
43 compact.soundSystem.playSelection(2);
44 pickup.soundSystem.turnOn();
45 } The trace immediately reflects the fact that the car has a CD player and the truck has a radio Once you click the stage (shown by the gap in the output that follows), the sound systems are used and the vehicles drive off into the sunset
[object Car] has high-performance radial tires
cd player added [object Car] changed gear [object Car] opened sunroof [object Truck] has storm-ready snow tires radio added
[object Truck] changed gear [object Truck] lowered tailgate [object Truck] turned on lights
Trang 9cd player selection: track 2
radio on
[object Car] 21 17
[object Truck] 16 22
Navigation Bar Revisited
Chapter 5 concluded with the start of a simple navigation bar created using
procedural programming techniques We’ll now step through a new exercise
to demonstrate one way to approach the same task using OOP This exercise
combines the use of standalone classes with classes that are linked to movie
clips in the main Flash file, LAS3Lab.fla—found in the nav_bar folder of the
chapter source archive
This exercise is also the start of the navigation system for the cumulative
book/companion website collective project In this chapter, we’ll use a basic
array to create five main buttons Later, in Chapter 14, we’ll add submenus to
this system and load all the content dynamically through the use of XML
The files and directories you create here will continue to be used and
enhanced throughout the remainder of this book, so establishing a logical
directory structure now will be very helpful The FLA and document class
should reside in the top level of a new directory Adjacent to the FLA, you’ll
eventually create two directories for classes In later versions of the exercise,
you’ll create a com folder for general packages that you may use in multiple
projects At this point, you’re ready to create an app folder for classes specific
to this project that you are less likely to reuse As always, adopting naming
conventions and organization recommendations are personal choices that
you can adapt when your comfort increases
The FLA requires two symbols in the library (included in the source):
MenuButtonMain
In our example, this is a movie clip that looks like a tab (Its name was
influenced by the fact that submenus will be introduced to this example,
later in the book.) The symbol’s linkage class is called MenuButtonMain,
too However, we’ll be using a custom class this time, rather than just
rely-ing on the automatic internal class created by Flash Professional for the
sole purpose of birthing the object with ActionScript Therefore, the fully
qualified path name, which includes not only the class name but also
its package, is used as the symbol’s linkage class:
com.learningaction-script3.gui.MenuButtonMain
HLineThick
This is simply a thick line, approximately 8 pixels tall and the width of
your file This serves as the horizontal plane on which the main menu
but-tons reside to form the navigation bar Unlike the button symbol, there’s no
N OT E
Push Yourself: A great way to make sure you understand packages is to reorganize the source files in the poly-morphism_inheritance exercise by put-ting the sound system files in their own package Pick a package name such as
app.las3.soundsystem, or try your own reverse domain path Don’t forget
to revise the package declaration line
in each affected class, and add import statements to the other classes referenc-ing your sound systems An example of this kind of organization can be found
in the polymorphism _packages direc-tory.
Trang 10external class for this line, as it has no functionality Still, we’ll give it a linkage class that includes a package location anyway: com.learningactionscript3 gui.HLineThick The result will be the same as using a class name without package information; Flash Professional will still create a placeholder class
in the SWF However, the nice thing about preplanning this way is that if you ever want to add functionality to this asset, you can create a class in this location and perhaps avoid additional edits to the FLA
Document class
The entry point to this project is the document class, LAS3Main.as, which
follows Lines 3 and 4 import the MovieClip class and custom NavigationBar
class, which you’ll create in a moment Line 6 declares the class and extends
MovieClip Lines 8 through 14 contain the class constructor
This navigation bar can feature a variable number of buttons, determined
by the contents of an array seen in lines 9 and 10 Lines 11 and 12 creates an instance of the NavigationBar class and passes in the array of labels for the new buttons Finally, line 13 adds the navigation bar to the display list
1 package {
2
3 import flash.display.MovieClip;
4 import com.learningactionscript3.gui.NavigationBar;
5
6 public class LAS3Main extends MovieClip {
7
8 public function LAS3Main() {
9 var menuData:Array = [ "one" , "two" , "three" ,
10 "four" , "five" ];
11 var navBar:NavigationBar =
12 new NavigationBar(menuData);
13 addChild(navBar);
14 }
15 }
16 }
NavigationBar
Next we need to create the NavigationBar class (NavigationBar.as), which will
be the home for our buttons Here we’ll focus on the times that are appreciably different in purpose from the prior class, or are otherwise noteworthy Line
1, for example is the package declaration discussed several times previously
in the book, but is worthy of mention because it reflects the location of the class—in the gui directory, within the com directory, found in the same folder
as the FLA Lines 9 through 12 contain the class constructor, populate the properties with the incoming argument data, and call the build() method:
1 package com.learningactionscript3.gui {
2
3 import flash.display.MovieClip;
4