< Day Day Up > |
We've done pretty well with our example Box class. We've given it methods to invoke and properties to examine and change. Now it's time to return to our Box constructor function, which we introduced much earlier in this chapter under "Constructor Functions (Take 1)." Suppose we want to make every Box instance we create start out with a width and height of 1. We need something that can set each new Box instance's width and height properties during the instance-creation process. That "something" is our constructor function. A constructor function is the metaphoric womb of a class; it isn't responsible for creating new instances, but it can be used to initialize each new instance. When we create a new instance of a class using the new operator, the class's constructor function runs. Within the constructor function, we can customize the newly created instance by setting its properties or invoking its methods. The instance can then be delivered to the world tailored to the situation at hand ”perhaps with width and height properties set to 1 (and big, cute, green eyes). To define a constructor function, we use the function statement within a class body, exactly as we'd define a method. However, a constructor function definition must also observe these rules:
Here's another look at the Box class constructor that we created earlier. Once again, lines 2 and 3 are the (empty) function ”the constructor function's body and the rest of the class are omitted from this example: class Box { public function Box ( ) { } }
In ActionScript 2.0, a constructor function's sole purpose is to initialize instances. Constructor functions are not mandatory (as they were in ActionScript 1.0). However, because most classes initialize their instances, most classes include a constructor function. As noted earlier, when a class does not define a constructor function explicitly, ActionScript automatically provides a default constructor that takes no parameters and performs no initialization on new instances of the class. Despite this convenience, as a best practice, always include a constructor, even if it is just an empty one. The empty constructor serves as a formal indication that the class design does not require a constructor and should be accompanied by a comment to that effect. For example: class Box { // Empty constructor. This class does not require initialization. public function Box ( ) { } }
Notice that the constructor function can be declared public or private , just like a normal method. The vast majority of constructor functions are public, but there are specific class designs that require a private constructor (for one example, see Chapter 17). Classes with private constructor functions cannot be instantiated directly. For example, if we supply a private constructor for our Box class: class Box { private function Box ( ) { } } and then we try to create a Box instance: var b:Box = new Box( ); the compiler generates the following error: The member is private and cannot be accessed. In order to allow instances to be created, a class with a private constructor must provide a class method that creates and returns instances. For example: class Box { // Private constructor private function Box ( ) { } // Class method that returns new instances public static function getBox ( ):Box { return new Box( ); } } // Usage: var b:Box = Box.getBox( ); If a class with a private constructor does not provide a public class method that calls the private constructor internally, you cannot instantiate objects of the class. You might use a private constructor in the following situations:
While constructors can be declared as public or private , they cannot be declared static . If you specify the static attribute in a constructor function definition, the compiler generates the following error: The only attributes allowed for constructor functions are public and private.
Luckily, the ActionScript compiler warns you as follows when the capitalization of your constructor's name does not match its class name: The member function '[FunctionName]' has a different case from the name of the class being defined, '[ClassName]', and will not be treated as the class constructor at runtime. Let's flesh out our basic Box constructor so that it assigns 1 to the width and height properties of every new Box instance created, as described in the earlier scenario. For clarity, we'll also show the width and height property definitions. By convention (but not by necessity), constructor functions are placed after property definitions but before method definitions. Note also that the constructor declaration does not include a return datatype, as they are prohibited for constructors: class Box { private var width:Number; private var height:Number; public function Box ( ) { // Initialize width and height width = 1; height = 1; } // Method definitions typically follow here... } Now every time we create a new Box instance, its width and height are initialized to 1 ( otherwise , they'd default to undefined ). That's pretty handy, but is also inflexible . To allow the width and height properties to be customized for each Box instance, we add parameters to our constructor function definition: public function Box (w:Number, h:Number) { // Initialize width and height , using the // values passed to the parameters w and h . width = w; height = h; } A constructor function's parameter values are passed to it via the new operator at object-creation time, as follows: new SomeClass ( value1 , value2 ,... valuen ); where SomeClass is the name of the class being instantiated and value1 , value2 , ... valuen are the values passed to the constructor function. For example, to create a new Box instance with an initial width of 2 and height of 3, we'd use: new Box(2, 3); Supplying parameter values to a constructor function is the ActionScript equivalent of genetically predetermining that your baby should be a girl, weigh 7 pounds , and have brown hair. Constructor functions normally use parameter values to set property values, but parameters can also more generally govern what should happen when an instance is created. For example, the constructor function for a Chat class might include a parameter, doConnect , that indicates whether the Chat instance should automatically connect to the chat server upon creation: class Chat { public function Chat (server:String, port:Number, doConnect:Boolean) { if (doConnect) { connect(server, port); } } } 4.5.1 Simulating Multiple Constructor FunctionsUnlike Java, ActionScript does not support multiple constructor functions for a single class (referred to as overloaded constructors in Java). In Java, a class can initialize an instance differently depending on the number and type of arguments used with the new operator. In ActionScript, similar functionality must be implemented manually. Example 4-5, based on our Box class, shows one possible way to simulate multiple constructor functions in ActionScript. Flash itself uses an analogous technique to allow Date instances to be created from a specific year, month, and day or from a count of milliseconds that have elapsed since January 1, 1970. In Example 4-5, the Box constructor delegates its work to three pseudo-constructor methods, named boxNoArgs( ) , boxString( ) , and boxNumberNumber( ) . Each pseudo-constructor's name indicates the number and datatype of the parameters it accepts (e.g., boxNumberNumber( ) defines two arguments of type Number ). Note that in this specific example the pseudo-constructors do not define datatypes for their arguments; this anomaly is discussed in the inline code comments. If some of the code in Example 4-5 is new to you, look for cross-references to related topics in the code comments. Example 4-5. Simulating overloaded constructorsclass Box { public var width:Number; public var height:Number; /** * Box constructor. Delegates initialization * to boxNoArgs( ) , boxString( ) , or boxNumberNumber( ) . */ public function Box (a1:Object, a2:Object) { // As we learned earlier, the arguments object stores the // argument values passed to this function. // If the constructor was invoked with no arguments, call boxNoArgs( ) . // If the constructor was invoked with one string argument, // call boxString( ) . If the constructor was invoked with // two numeric arguments, call boxNumberNumber( ) . if (arguments.length == 0) { boxNoArgs( ); } else if (typeof a1 == "string") { // In the following line of code, we'd normally have to cast a1 to the // type required by the boxString( ) method's first parameter (in // this case, String). However, the ActionScript 2.0 cast operator // does not work with the String and Number datatypes, so, // unfortunately, we must leave the parameters for boxString( ) and // boxNumberNumber( ) untyped. For details on this casting problem, // see Chapter 3. boxString(a1); } else if (typeof a1 == "number" && typeof a2 == "number") { // No cast to Number here either; see previous comment. boxNumberNumber(a1, a2); } else { // Display a warning that the method was used improperly. trace("Unexpected number of arguments passed to Box constructor."); } } /** * No-argument constructor. */ private function boxNoArgs ( ):Void { // arguments.caller is a reference to the function that called // this function. // If this method was not called by the Box constructor, then exit. if (arguments.caller != Box) { return; } // Supply a default width and height. width = 1; height = 1; } /** * String constructor. */ private function boxString (size):Void { // If this method was not called by the Box constructor, then exit. if (arguments.caller != Box) { return; } // Set width and height based on a descriptive string. if (size == "large") { width = 100; height = 100; } else if (size == "small") { width = 10; height = 10; } else { trace("Invalid box size specified"); } } /** * Numeric constructor. */ private function boxNumberNumber (w, h):Void { // If this method was not called by the Box constructor, then exit. if (arguments.caller != Box) { return; } // Set numeric width and height . width = w; height = h; } } // Usage: var b1:Box = new Box( ); trace(b1.width); // Displays: 1 var b2:Box = new Box("large"); trace(b2.width); // Displays: 100 var b3:Box = new Box(25, 35); trace(b3.width); // Displays: 25 4.5.2 Using this in Constructor FunctionsWithin the body of a constructor function, the this keyword refers to the newly created instance. We use this in a constructor function exactly as we use it from within instance methods. For example, the following code uses this to resolve a parameter/property name conflict: public function Box (width:Number, height:Number) { // Sets width property ( this.width ) to value of width parameter ( width ). this.width = width; // Sets height property ( this.height ) to value // of height parameter ( height ). this.height = height; } For details on using this , see the earlier discussion under "Referring to the Current Object with the Keyword this ." 4.5.3 Constructor Functions Versus Default Property ValuesEarlier in this chapter we learned that an instance property can be assigned a default value provided the value is a compile-time constant expression, such as 10 or "hello world". For example: private var x:Number = 10; private var msg:String = "hello world"; While it's legal to initialize an instance property by assigning it a default value, it's a best practice to perform all instance property initialization in a constructor function. Constructor functions are not limited by the compile-time constant rule, so they can safely calculate property values with arbitrary code such as method calls, conditionals, and loops . Furthermore, by keeping property initialization in constructors, we make our class's initialization code easy to find and maintain. |
< Day Day Up > |