18
class

implementing new object types

actionscript provides mechanisms for defining new object types. when we employ them, we extend the functionality available to our programs. when we make new objects self-contained and free of dependencies on other objects, we are following best practices for creating loosely-coupled code and are making our code easier to reuse in multiple situations.

class files

new object types are called classes and are implemented in external text files. the text file must be named to match the class it implements: MyNewClass would be defined in a file named MyNewClass.as .

creating class files

class files can be written in any text editor, but conveniences like syntax hilighting, auto-indenting, code insight, syntax checking, and code folding make some editors more comfortable than others.

the flash authoring environment can be used to author class files: the resources page lists other text editors popular for ActionScript editing.

class structure

all classes in ActionScript 3 must be declared within a package, as a way of organizing and preventing naming conflicts. the package keyword will be at the start of the file, optionally with a package name if the class is being nested within a folder structure, and then with a matched pair of curly braces to enclose the class code. for simple applications with few custom classes, it's often convenient to just keep the classes in the same folder as the .fla and leave the package name blank:

package {
}

within the package we can define a new class with the public and class keywords, meaning we're defining a template for making instances of a new datatype and making it available to use by any code that imports the file.

public class Shape {
}
we can add properties and methods by just declaring them inside the code brackets of the class as we would declare them on a timeline script in flash:
package {
   
   public class Noun {
      
      private var hello:String
      
      public function Noun(adj:String, noun:String) {
         hello = "Hello, I am a " +adj +" " +noun;
         trace(hello);
      }
   }
}
one special method bears the same name as the class, and is called the constructor method. this function is executed when the class is instantiated (new Noun()), and gives us a chance to initialize variables and do any other work required to make the class ready for use.

importing

once we have an existing class file, we can bring it into scope for use in other code by using the key word import, and then the keyword new to instantiate the class:
import Noun;

var p:Noun = new Noun("big", "pumpkin");
this should already feel familiar after working with the Point and Graphics classes.

packaging

what if today i want to implement a class that shows various videos, and tomorrow one that implements a participant in a game? i might want to call each of them Player, but i can't have two files with the same name in the same directory. even if the names didn't conflict, it would be unorganized to store them in the same directory when they apply to different projects.

a better approach is to use packages—nested folders that create a unique path to a file, and incorporation of that unique path into the file name. so util/video/Player.as would contain the class util.video.Player and games/poker/Player.as would contain the class games.poker.Player .

the import statement creates a shortcut to the fully qualified path name, so we can use just the name of the class:
import util.video.Player;
var P:Player = new Player();
instead of the fully qualified class name:
var P:util.video.Player = new util.video.Player();
however, if we had a need to use two classes of the same name in the same file, we would have to use their fully qualified names, since the import statement would no longer be useful.

finding

in order for flash to be able to import and compile classes we've written, it has to be able to locate them. we must ensure the path to the root of our class package is in the list of classpaths that flash stores.

there are actually two places flash stores classpaths:

  1. at the application level (set via [CTRL-U or Edit / Preferences] / ActionScript tab / Actionscript 3.0 Settings button)
  2. at the file level (set via [CTRL-SHFT-F12 or File / Publish Settings] / Flash tab / Settings button (next to the actionScript version drop-down))
as long as one place has the right path, flash will locate your classes.

note that relative paths can be used, such as . (period) for current directory, or ../lib for one directory up and in the lib folder.

composition and extension

classes should be designed to be modular, so that simple or basic classes can be assembled or extended into more complex classes.

has-a

we can instantiate one class within another, to define a custom datatype for storing information in a way that will be convenient to manipulate. this is typically described as a has-a relationship: a car has an engine, or a rectangle has four points.

is-a

classes can also extend other classes by inheriting everything from the class they extend, and then adding new properties and methods or overwriting existing ones. this is typically described as an is-a relationship: a car is a vehicle, or a rectangle is a shape.

the class that gets extended is called the base class or super class, while the class doing the inheriting is called a sub class.

to extend another class, we use the keyword extends in the declaration of the class, and the keyword super to call the constructor function of the base class:
class ExtendedGraphics extends Graphics {
    function ExtendedGraphics() {
        super();
    }
}
many built-in flash classes can be extended too, like Sprite, Array, Date, or MovieClip.

adding code behind a symbol

extending MovieClip is powerful. you can create a movie clip symbol in the library, right-click and in the linkage properties dialog, link it to a class file. this allows you to write complex object-oriented code in an external library file that can be reused from many places for many different symbols, and marry it to complex animated graphics. you can even do this a the root level, extending the root MovieClip to provide an external implementation. Just specify the class name in the Document class field of the movie properties panel.

the flash display API

all visual classes in Flash are packaged under flash.display. this diagram illustrates their lineage.

 

exposing or protecting properties

one benefit of compile-time checking is the ability to limit access to parts of a class.

public

with the public keyword, we can declare properties and methods fully accessible from outside the class. these are the Application Programming Interface, or API; the dials, knobs, and buttons that the user is given to manipulate.
public function draw() {}

private

with the private keyword, we can declare some properties and methods inaccessible from outside the class, to prevent values important to the function of the class from being modified by any other code. if no access modifier is specified, flash defaults to public access.
private var type:String;

accessors and mutators

another technique for protecting internal properties involves hiding them behind public functions. to allow the user to access a private property, we can provide a getProperty method, that returns the value of the private property. to allow users to modify a private property, we can provide a setProperty method that accepts a new value for the property as a parameter, and updates internal state accordingly.

this technique gives us a better level of abstraction than just declaring a property public. we don't even neccessarily need an actual property; the user won't care as long as they get a value back when they call the accessor method, and can set the value with the mutator method.

actionscript goes a step further in an attempt to simplify the use of accessors and mutators. by supporting special get and set keywords, you can implement accessor and mutator functions that create a virtual public property:

class SingleLetter {
    
    private var _letter:String;
    private static var alphabet:String;
    
    
    public function SingleLetter(c:String) {
        // initialize set of valid letters
        alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
                   "abcdefghijklmnopqrstuvwxyz";
        // call letter mutator function to get error checking
        this.letter = c;
    }
    
    public function set letter(c:String):Void {
        // only accept valid letters
        if (alphabet.indexOf(c) > -1) {
            _letter = c;
        }
        else {
            trace("SingleLetter only accepts letter values.");
        }
    }
    public function get letter():String {
        // only return uppercase letters
        return _letter.toUpperCase();
    }

}
now users of the class can access the letter property directly, but the implementation of the class has the opportunity to execute additional code any time the letter value is requested or asked to be changed.
var L:SingleLetter = new SingleLetter("q");
trace(L.letter);

implementing interfaces

classes can inherit from only one class (datatype) at a time. yet sometimes it's important to guarantee that a class will support certain methods, regardless of its datatype.

with the keyword interface, we can declare an abstract class that defines only method names, not their implementation. other classes can use the keyword implements in their class declaration to claim compliance with one or more interfaces and provide their own datatype-specific implementation.

for example, maybe we'd like to have a Point class and a Shape class. both of these are generic, separate concepts and make sense to be base classes. a Point is not a type of Shape, nor is a Shape a type of Point. so neither will inherit from the other. yet it would be convenient to be able to transform instances of each, so maybe we'd like to ensure that all instances of Point and Shape support the methods translate(), rotate(), and scale(). we can do this with an interface:
interface Transformable {
    function translate(deltax:Number, deltaY:Number):Void;
    function rotate(angle:Number, center:Point):Void;
    function scale(deltax:Number, deltaY:Number, center:Point):Void;
}
and then both Shape and Point can implement the Transformable interface, guaranteeing that they will provide implementations for the transform methods specified in the interface.
class Point implements Transformable {}
class Shape implements Transformable {}

class and instance properties

properties and methods can be implemented on one of two levels: at the class level, meaning one copy of the property or method is shared between all instances of the class, or at the instance level, meaning every new instance of the class has its own copy of the data.

by default, all properties and methods are instance level properties and methods. often, this is the desired behavior. if i create a Point class, i want every instance to able to have different x and y values.

static

sometimes, though, one copy is all that is needed. it doesn't make sense to keep multiple copies of a constant value, like PI, or a method that recieves all the information it needs through its parameters. for these cases, we can use the keyword static in the declaration of the property or method to keep a single copy of it at the class level that will be shared by all instances of the class.
private static TWO_PI:Number = 2 * Math.PI; 
public static multiply(a:Number, b:Number) { return a * b; }