6.4 Overriding Methods and Properties

 <  Day Day Up  >  

6.4 Overriding Methods and Properties

In our study of inheritance so far, we've covered reuse , in which a subclass uses its superclass's methods and properties, and extension , in which a subclass adds its own methods and properties. We'll now turn to redefinition , in which a subclass provides an alternative version of a method or property in its superclass. (Bear in mind that reuse, extension, and redefinition are not mutually exclusive. A subclass might employ all three techniques in regard to the superclass's members .) Redefinition lets us customize an existing class for a specific purpose by augmenting, constraining, or even nullifying one or more of its original behaviors. Redefining a method or property is known technically as overriding that method or property.

ActionScript allows any of a class's members (that is, static properties, instance properties, static methods, and instance methods) to be redefined. We'll take a look at the most typical kind of redefinition first: overriding instance methods.

6.4.1 Overriding Instance Methods

To override a superclass's instance method, we simply supply an instance method definition of the same name in the subclass. For example, in the following code, in which B is a subclass of A , the method B.x( ) overrides the method A.x( ) :

 class A {   // Declare an instance method in the superclass   public function x ( ):Void {     trace("A.x( ) was called.");   } } // Class B is a subclass of class A class B extends A {   // Override the superclass's method of the same name   public function x ( ):Void {     trace("B.x( ) was called.");   } } 

When x( ) is invoked on an instance of class A , the interpreter uses A 's definition of the method. But when x( ) is invoked on an instance of class B , the interpreter uses B 's definition of the method instead of class A 's definition:

 var aInstance:A = new A( ); aInstance.x( );  // Displays: A.x( ) was called. var bInstance:B = new B( ); bInstance.x( );  // Displays: B.x( ) was called. 

Let's consider a more applied example. Suppose we're building a geometry simulation that depicts rectangles and squares. To handle the rectangles, we create a Rectangle class, as follows :

 // This code must be placed in an external file named   Rectangle.as   class Rectangle {   private var w:Number = 0;   private var h:Number = 0;   public function setSize (newW:Number, newH:Number):Void {     w = newW;     h = newH;   }   public function getArea ( ):Number {     return w * h;   } } 

To handle squares, we could create a completely unrelated Square class. But a square is really just a rectangle with sides of equal width and height. To exploit that similarity, we'll create a Square class that extends Rectangle but alters the setSize( ) method to prevent w and h from being set unless newW equals newH . The constraint applies only to squares, not to rectangles in general, so it doesn't belong in the Rectangle class.

Here's the Square class, showing the overridden setSize( ) method:

 // This code must be placed in an external file named   Square.as   class Square extends Rectangle {   public function setSize (newW:Number, newH:Number):Void {     // Here's the constraint introduced by the   Square   class.     if (newW == newH) {       w = newW;       h = newH;     }   } } 

A real-world version of Square.setSize( ) might accept only one argument for the side length, instead of accepting two potentially different parameters. This eliminates the need to check whether the sides are equal (although we still want to check that the side length is a positive number). For example:

 public function setSize (sideLength:Number):Void {   if (sideLength > 0) {     w = sideLength;     h = sideLength;   } } 

However, our current focus is adding constraints to a method without changing the method's signature, so we'll stick with our newW and newH parameters for the sake of this example.

When setSize( ) is invoked on a Square or Rectangle instance, the interpreter uses the version of the method that matches the actual class of the instance.

When an instance method is invoked on an object, the interpreter first searches for the instance method defined on the class used to instantiate the object.


For example, in the following code, we invoke setSize( ) on a Rectangle instance. The interpreter knows that the instance's class is Rectangle , so it invokes Rectangle.setSize( ) :

 var r:Rectangle = new Rectangle( ); r.setSize(4,5); trace (r.getArea( ));  // Displays: 20 

By contrast, in the following code, we invoke setSize( ) on a Square instance. This time the interpreter knows that the instance's class is Square , so it invokes Square.setSize( ) , not Rectangle.setSize( ) :

 var s:Square = new Square( ); s.setSize(4,5); trace (s.getArea( ));  // Displays: 0 (The   setSize( )   method prevented the                       //              illegal property assignment.) 

In the preceding code, the output of s.getArea( ) ” 0 ”indicates that values of w and h were not set properly by the call to s.setSize( ) ; the Square.setSize( ) method sets w and h only when newW and newH are equal.

But what if the declared datatype doesn't match the type of instance stored in a variable?

Even if the datatype of s is declared as Rectangle , because it stores a Square instance, the interpreter uses the version of the method from the instance's actual class (namely Square ).


Consider this example:

 // Datatype is declared as   Rectangle   ... var s:Rectangle = new Square( ); s.setSize(4,5); // ...but   Square.setSize( )   was still used! trace (s.getArea( ));  // Displays: 0 

Similarly, even if the instance s were cast to the Rectangle class, the interpreter would still use the version of the method from the instance's actual class (which, again, is Square ):

 var s:Square = new Square( ); // Instance   s   is cast to   Rectangle   . Rectangle(s).setSize(4,5); // ...but   Square.setSize( )   was still used. trace (s.getArea( ));  // Displays: 0 

In all cases, instance method calls on an object remain true to that object's actual class!

Notice also that a cast does not change the class of the object. A cast only instructs the compiler and interpreter to treat an object as though it were of a specified type. For more information on casting, see "Casting" in Chapter 3.

6.4.2 Invoking an Overridden Instance Method

When a subclass overrides an instance method, the superclass's version of the method is not lost. It remains accessible to instances of the subclass via the super operator, which can invoke an overridden method as follows:

 super.   methodName   (   arg1   ,   arg2   , ...   argn   ); 

where methodName is the name of the overridden method to invoke, and arg1 , arg2 , ... argn are the arguments to pass to that method. (We'll learn more about other uses of super later in this chapter.)

Let's see how to use super to invoke a method of the superclass that is overridden. So far, our Square.setSize( ) method needlessly duplicates the code in the Rectangle.setSize( ) method. The Rectangle version is:

 public function setSize (newW:Number, newH:Number):Void {   w = newW;   h = newH; } 

The Square version of setSize( ) merely adds an if statement:

 public function setSize (newW:Number, newH:Number):Void {   if (newW == newH) {     w = newW;     h = newH;   } } 

To avoid the duplication of setting w and h in both methods, we can use super , as shown in this revised version of Square.setSize( ) :

 public function setSize (newW:Number, newH:Number):Void {   if (newW == newH) {     // Invoke the superclass's   setSize( )   method, in this case     //   Rectangle.setSize( )   , on the current instance.     super.setSize(newW, newH);   } } 

The revised Square.setSize( ) method checks if newW and newH are equal; if they are, it invokes Rectangle.setSize( ) on the current instance. The Rectangle.setSize( ) method takes care of setting w and h .

The setSize( ) method example shows how a subclass can override a method to constrain its behavior. A subclass can also override a method to augment its behavior. For example, we might create ScreenRectangle , a subclass of Rectangle that draws a rectangle to the screen. The subclass in the following code adds a draw( ) method and augments setSize( ) . The ScreenRectangle.setSize( ) method retains the behavior of the overridden Rectangle.setSize( ) but adds a call to draw( ) , so the rectangle changes size on screen whenever setSize( ) is invoked. Here's the code:

 class ScreenRectangle extends Rectangle {   public function setSize (newW:Number, newH:Number):Void {     // Call   Rectangle.setSize( ).   super.setSize(newW, newH);     // Now render the rectangle on screen.     draw( );   }   public function draw ( ):Void {     // Screen-rendering code goes here.     // For example rendering code, see Example 4-6.   } } 

Finally, overriding can be used to nullify the behavior of a method. The technique is straightforward: the subclass's version of the overridden method simply does nothing. For example, the following code shows a subclass named ReadOnlyRectangle that disables the Rectangle.setSize( ) method, preventing an instance from changing size:

 class ReadOnlyRectangle extends Rectangle {   // This effectively disables the   setSize( )   method   // for instances of the   ReadOnlyRectangle   class.   public function setSize (newW:Number, newH:Number):Void {     // Do nothing.   } } 

Nullifying a method is legal but, in most cases, not considered good OOP practice. Generally speaking, every subclass should support the methods of its superclass(es). That way, external code can safely use the superclass's methods on instances of the subclass (as we'll see later, treating unlike objects alike is an important part of polymorphism ).

6.4.3 Overriding Class Methods

We saw earlier how to override instance methods of a superclass. To override a superclass's class method (i.e., a method declared with the static attribute), we simply add a static method definition of the same name in the subclass. For example, in the following code, the static method B.m( ) overrides the static method A.m( ) :

 class A {   public  static  function m ( ):Void {     trace("A.m( ) was called.");   } } class B extends A {   public  static  function m ( ):Void {     trace("B.m( ) was called.");   } } 

When invoking a class method, whether or not overridden, you should provide the class name as part of the method invocation. For example, to invoke B 's version of m( ) , you should use B.m( ), and to invoke A 's version of m( ) , you should use A.m( ) . For example:

 A.m( );    // Displays: A.m( ) was called B.m( );    // Displays: B.m( ) was called m( );      // Avoid this unqualified method call 

In fact, if you are accessing the method from code outside the class, you must include the class name. However, from within the class that defines the method, you can legally refer to the method without the qualifying class name (e.g., m( ) instead of B.m( ) ). This practice, while legal, is not recommended. We'll learn why under Section 6.4.6 later in this chapter.

You may wonder what happens if you declare a nonstatic method in a subclass in an attempt to overwrite a static method of the same name defined in the superclass. Consider this code example:

 class A {   // The   static   keyword defines a class method   public  static  function m ( ):Void {     trace("A.m( ) was called.");   } } class B extends A {   // Note the omission of the   static   keyword, so this is an instance method   public function m ( ):Void {     trace("B.m( ) was called.");   } } 

The preceding code effectively defines a new instance method, m( ) , for instances of class B , but it has no effect on the class method of the same name defined in class A . Therefore, the method the interpreter invokes depends on which object is used in the invocation. For example, in the following code, you might be surprised by the results of invoking B.m( ) :

 A.m( );    // Displays: A.m( ) was called B.m( );    // Displays: Error! The property being referenced does            // not have the static attribute. 

In the preceding code, because there is no class method (i.e., static method) named m( ) defined on the class B , invoking B.m( ) actually causes a compile-time error. The m( ) method in class B is declared as an instance method, so it must be invoked on an instance as follows:

 bInstance:B = new B( ); bInstance.m( );   // Displays: B.m( ) was called 

You wouldn't ordinarily define class methods (i.e., static methods) and instance methods (i.e., nonstatic methods) of the same name. Therefore, in the preceding example, if your invocation of B.m( ) causes an error, you probably just forgot the static attribute when defining the m( ) method within the B class.

Note that the super operator cannot be used to invoke a superclass's version of a class method from within the subclass version. The keyword super can be used only in instance methods and constructor functions. To access the superclass's version of a class method, reference the superclass in the method invocation, as in:

 SomeSuperClass.someOverriddenMethod( ); 

6.4.4 Overriding Properties

Just as instance and class methods can be overridden, so can instance and class properties. To override a superclass's property, we add a property definition of the same name in the subclass. For example, in the following code, for instances of class B , the instance property B.p overrides the instance property A.p :

 class A {   public var p:Number = 10; } class B extends A {   public var p:Number = 15; } 

Remember that overriding a property using a subclass declaration has no effect on instances of the superclass. Therefore, all instances of class A have an initial value of 10 for the property p . However, because p is overridden in the subclass, all instances of class B have an initial value of 15 for the property p :

 var aInstance:A = new A( ); trace(aInstance.p);  // Displays: 10 var bInstance:B = new B( ); trace(bInstance.p);  // Displays: 15 

Any kind of property can override any other kind of property. That is, an instance property can override an instance property or a class property; a class property can override a class property or an instance property. However, we're sure to instigate mass confusion if we override an instance property with a class property or vice versa.

For the sake of code clarity, you should override instance properties with only other instance properties, and override class properties with only other class properties.


The preceding example overrode a superclass's instance property with an instance property in the subclass. Similarly, here's an example of overriding a superclass's class property (declared static ) with a class property in the subclass:

 class A {   // Create a class property,   s   public static var s:Number = 20; } class B extends A {   // Override the class property,   s   public static var s:Number = 25; } 

When a class property is overridden, it is not lost and can still be accessed via the superclass. For example:

 class A {   public static var s:Number = 20; } class B extends A {   public static var s:Number = 25;   public function showS ( ) {     // Show the subclass version of   s   .     trace("B.s is: " + B.s);          // Show the superclass version of   s   .     trace("A.s is: " + A.s);   } } // Usage: var bInstance:B = new B( ); bInstance.showS( );  // Displays:                     // B.s is: 25                     // A.s is: 20 

Note, however, that the super keyword, which we used earlier to access an overridden instance method, cannot be used to reference an overridden class property. For example, if in B.shows( ) we change the statement:

 trace("A.s is: " + A.s); 

to:

 trace("A.s is: " + super.s); 

the resulting output is:

 A.s is: undefined 

The preceding use of super causes the following error when Check Syntax (Tools Check Syntax) is performed on class B in Flash MX Professional 2004's script editor:

 Static members can only be accessed directly through classes. 

Unfortunately, due to a bug in Flash MX 2004, the compiler does not generate the same compile-time error when the class is used in a .fla file.

Unlike a class property, an instance property that is overridden does not continue to exist independently. Rather, a single instance property with a single value exists for each instance of the overriding subclass. For example, in the following code, class A defines a property, p , that is overridden by class B . An instance of class B maintains a single value for the property p , despite whether p is assigned by methods in the superclass or the subclass.

 class A {   public var p:Number;   public function setTo20 ( ):Void {     p = 20;   } } class B extends A {   public var p:Number;   public function setTo10 ( ):Void {     p = 10;   } } // Usage example: var bInstance:B = new B( ); // Use superclass method to set   p   . bInstance.setTo20( ); trace(bInstance.p); // Displays: 20 // Use subclass method to set   p   . bInstance.setTo10( ); trace(bInstance.p); // Displays: 10 

This single-property-value behavior contrasts with Java, in which the subclass maintains a memory slot both for its own definition of the overridden property and for its superclass's definition. In Java, the equivalent code would output:

 0 10 

because, in Java, invoking bInstance.setTo20( ) would modify the superclass version of the property p , not the subclass version because setTo20 ( ) is defined in the superclass despite being invoked on a subclass instance. In ActionScript, no such separation exists. Even when A.setTo20( ) is invoked via the keyword super , the property affected is still the single property p . The following code demonstrates . It adds a new method, callSetTo20( ) to class B (class A remains unchanged):

 class B extends A {   public var p:Number;   public function setTo10 ( ):Void {     p = 10;   }   public function callSetTo20 ( ):Void {     super.setTo20( );   } } // Usage example: var bInstance:B = new B( ); // Use superclass method to set   p   . bInstance.setTo20( ); trace(bInstance.p); // Displays: 20 // Use subclass method to set   p   . bInstance.setTo10( ); trace(bInstance.p); // Displays: 10 // Use superclass method through   super   to set   p   . bInstance.callSetTo20( ); trace(bInstance.p); // Displays: 20 (not 10, the previous value) 

Again, in Java, the last line of the equivalent code would yield 10, not 20, because calling bInstance.callSetTo20( ) sets the superclass's version of the property p , not the subclass's version. Java displays the value of the subclass's version of the property, which would be 10. For comparison, Example 6-2 lists the Java code for classes A and B .

Example 6-2. Overridden property access in Java
 public class A {   public int p;   public void setTo20 ( ) {     p = 20;   } } public class B extends A {   public int p;      public void setTo10 ( ) {     p = 10;   }   public void callSetTo20 ( ) {     super.setTo20( );     // Check superclass value for   p   , after setting it.     System.out.println("Superclass version of p is: " + super.p);   }   public static void main (String [] args) {     B b = new B( );     // This line affects the superclass version of   p   .     b.setTo20( );     // The subclass version is  not   affected  .     System.out.println("Subclass version of p is: " + b.p);     // This line affects the subclass version of   p   .     b.setTo10( );     // The subclass version  is   affected  .     System.out.println("Subclass version of p is: " + b.p);     // This line affects the superclass version of   p   .     b.callSetTo20( );     // The subclass version is  not   affected  .     System.out.println("Subclass version of p is: " + b.p);   } } // Output: Subclass version of p is: 0 Subclass version of p is: 10 Superclass version of p is: 20 Subclass version of p is: 10 

6.4.4.1 The super keyword and overridden property access

In ActionScript 2.0, we cannot use super to retrieve the value of an overridden instance property because, as we just learned, instance properties that are overridden do not continue to exist independently.

However, strangely enough, if an overridden instance property is initialized in the superclass body (outside of a method or the constructor), then that initial value is permanently accessible via super :

 class A {   // Initialize the property   p   to 10.   public var p:Number = 10; } class B extends A {   public var p:Number = 15;   public function showP ( ):Void {     // Show the value of   p.   trace("p is: " + p);     // Modify the value of   p   , then show the modified value.     p = 5;     trace("p is: " + p);          // Show the initial value of   p   , assigned by      // the superclass. This will yield 10 even though   p   has     // been modified on the current object.     trace("super.p is: " + super.p);   } } // Usage: var bInstance:B = new B( ); bInstance.showP( );  // Displays:                     // p is: 15                     // p is: 5                     // super.p is: 10 

This uncommon super behavior is caused by the compiler's automatic conversion of ActionScript 2.0 code to ActionScript 1.0 code (which is a necessary evil guaranteeing that most ActionScript 2.0 code will work in Flash Player 6). The behavior may be changed in future versions of the language. You should, therefore, avoid using it.

6.4.5 Member Access from Inherited, Overriding, and Overridden Instance Methods

We've now seen how to override instance methods, static methods, instance properties, and static properties. We've also seen how to invoke overridden instance methods. With all these overridden methods and properties afoot, we're forced to consider some tricky scope questions. For example, from within an instance method that overrides another instance method, what properties and methods are accessible? What about from within the overridden version of the method? What if the properties and methods being accessed are, themselves , overridden?

Before we can answer these questions, we need a short lesson on how the compiler resolves unqualified member references . To qualify a reference is to include explicitly the object or class to which the property or method pertains.

Therefore, an unqualified member reference is any mention of a property or method that does not explicitly include the name of an object or class. For example, this is an unqualified reference to the method x( ) :

 x( ) 

By contrast, this reference to x( ) is qualified because it specifies the object on which x( ) should be invoked ( this , the current object):

 this.x( ) 

Here is an unqualified reference to the property p :

 p 

By contrast, this reference to the property p is qualified because it specifies the class on which p is defined ( A ):

 A.p 

When a class is compiled, if the compiler encounters an unqualified member reference, it deduces the object or class to which the member pertains and permanently adds that object or class to the reference.


That is, at compile time, all unqualified member references are permanently converted to qualified member references of the form:

   objectName   .   memberName   

or

   ClassName.memberName   

If you don't specify an object or class when referring to a member in your source code, the compiler will supply the object or class it thinks is correct. The compiler chooses the "correct" object or class based on the name of the member and the compile-time context in which the member reference occurs.

For example, in the following class, A , we define one instance property ( instanceProp ) and one class property ( classProp ). We make unqualified references to those properties in the methods getClassProp( ) and getInstanceProp( ) :

 class A {   public static var classProp:Number = 0;   public var instanceProp:Number = 1;   public function getClassProp ( ):Number {     return classProp;   }   public function getInstanceProp ( ):Number {     return instanceProp;   } } 

After compilation, the interpreter effectively treats the code as follows (note the addition of A . and this ., shown in bold):

 class A {   public static var classProp:Number = 0;   public var instanceProp:Number = 1;   public function getClassProp ( ):Number {     return  A  .classProp;   }   public function getInstanceProp ( ):Number {     return  this  .instanceProp;   } } 

The compiler knows that classProp should be treated as A.classProp because the classProp property is defined as static . Likewise, the compiler knows that instanceProp should be treated as this.instanceProp because the instanceProp property has no static attribute, so it must be an instance property accessed via the current object (i.e., this ).

It's not uncommon or even bad form to use unqualified member references. Most of the time, the compiler's automatic resolution of those references is intuitive. However, as we'll see next , when members are accessed from overridden and inherited methods, the compiler's behavior is less obvious. Therefore, to avoid adding confusion to an already complex situation, it's wise to fully qualify all member references when using inherited or overridden methods.

Now that we understand how the compiler resolves unqualified member references, let's consider the three possible member access scenarios:

  • Member access from an inherited instance method

  • Member access from an overriding instance method

  • Member access from an overridden instance method invoked via super

In the preceding scenarios, we'll explore what happens when each of the following members are accessed:

  • An instance method defined in the subclass

  • An inherited instance method

  • An overridden instance method

  • An instance property defined by the subclass

  • An inherited instance property

  • An overridden instance property

  • A class property defined by the subclass

  • An inherited class property

  • An overridden class property

  • A class method defined by the subclass

  • An inherited class method

  • An overridden class method

We'll also point out tricky cases in which unqualified member resolution can lead to potentially surprising results. You don't need to memorize all the behaviors now, but it is a good idea to skim the next pages to better understand scope issues. If you learn to recognize whether you are, say, accessing an inherited class property from an overridden class method, you can refer back to this chapter for the necessary details on scope resolution.

6.4.5.1 Member access from an inherited instance method

As we learned earlier, an inherited instance method is an instance method defined in the superclass but used by the subclass. For example, in the following code, class B inherits the method methOfA( ) from class A :

 class A {   public function methOfA ( ):Void {   } } class B extends A { } 

An instance of B can invoke methOfA( ) as follows:

 var bInstance:B = new B( ); bInstance.methOfA( ); 

Table 6-3 describes what happens when bInstance invokes methOfA( ) and methOfA( ) accesses various methods and properties.

Table 6-3. Member access from an inherited instance method

Member accessed

Example

Notes

Instance method of subclass

 class A {  public function methOfA( ):Void {   methOfB( );  // Error!  } } class B extends A {  public function methOfB( ):Void {  } } 

Compile-time error. Methods of a superclass can't refer to methods defined only on a subclass, and Class A does not define methOfB( ) , so the reference is illegal.

Inherited instance method (defined in superclass)

 class A {  public function methOfA( ):Void {   // Executes   otherMethOfA( )       otherMethOfA( );      }  public function otherMethOfA( ):Void {  } } class B extends A { } // Usage var bInstance:B = new B( ); bInstance.methOfA( );    // Executes properly 

One method of a class can call another method of the same class, even when invoked via an instance of a subclass.

Overridden instance method (defined in both superclass and subclass)

 class A {  public function methOfA ( ):Void {   // Executes   B   's version of   overriddenMeth( )   if   // invoked on an instance of class   B   .    // Executes   A   's version of   overriddenMeth( )   if   // invoked on an instance of class   A   .   overriddenMeth( );   }  public function overriddenMeth( ):Void {  } } class B extends A {  public function overriddenMeth( ):Void {  } } // Usage var aInstance:A = new A( ); var bInstance:B = new B( ); // Invokes   A   's version of   overriddenMeth( ).   aInstance.methOfA( ); // Invokes   B   's version of   overriddenMeth( ).   bInstance.methOfA( ); 

The version of the overridden method executed depends on the class of the instance on which the method is invoked.

Instance property of subclass

 class A {  public function methOfA( ):Void {   trace(propOfB);  // Error!  } } class B extends A {  public var propOfB:Number = 1; } 

Compile-time error. Methods of a superclass can't refer to properties defined only on a subclass, and class A does not define propOfB , so the reference is illegal.

Inherited instance property (defined in superclass)

 class A {  public var propOfA:Number = 2;  public function methOfA( ):Void {   trace(propOfA);   // Displays: 2  } } class B extends A { } 

A method of the superclass, such as methOfA( ) , can access properties of the superclass, such as propOfA .

Overridden instance property (defined in both superclass and subclass)

 class A {  public var overriddenProp:Number = 3;  public function methOfA( ):Void {   trace(overriddenProp);    } } class B extends A {  public var overriddenProp:Number = 4; } // Usage var aInstance:A = new A( ); var bInstance:B = new B( ); aInstance.methOfA( );  // Displays: 3 bInstance.methOfA( );  // Displays: 4 

Even though the property is accessed in class A , when methOfA( ) is invoked on an instance of B , the subclass's value of the overridden property (4, not 3) appears because bInstance can store only one value in overriddenProp . (In Java, calls to methOfA( ) would always display 3.)

Class method of subclass, unqualified reference [1]

 class A {  public function methOfA( ):Void {   classMethOfB( );  // Error!  } } class B extends A {  public static function classMethOfB( ):Void {  } } 

Compile-time error. Class A does not define classMethOfB( ) , and methods of a superclass can't resolve unqualified references to class methods of a subclass, so the reference is illegal.

Class method of subclass, qualified reference [2]

 class A {  public function methOfA( ):Void {   B.classMethOfB( );  // Executes properly  } } class B extends A {  public static function classMethOfB( ):Void {  } } 

B.classMethOfB( ) is a qualified reference, so the compiler knows to look on class B for this class method (this is true for all classes, independent of whether B is a subclass of A ).

Inherited class method (defined in superclass) [2]

 class A {  public static function classMethOfA( ):Void {  }  public function methOfA( ):Void {   // Executes   A.classMethOfA( )       classMethOfA( );     } } class B extends A { } // Usage var bInstance:B = new B( ); bInstance.methOfA( ); // Invokes   A.classMethOfA( )   

At compile time, unqualified references to class methods are resolved relative to the current class. So classMethOfA( ) resolves to A.classMethOfA( ) . A.classMethOfA( ) executes even when invoked from an instance of subclass B .

Overridden class method (defined in both superclass and subclass) [2]

 class A {  public static function overriddenClassMeth( ):Void {  }  public function methOfA( ):Void {   // Executes   A.overriddenClassMeth( )   overriddenClassMeth( );  } } class B extends A {  public static function overriddenClassMeth( ):Void {  } } // Usage--compare also with preceding example var bInstance:B = new B( ); // Invokes   A.overriddenClassMeth( )   bInstance.methOfA( ); 

At compile time, unqualified references to class methods are resolved relative to the current class. From within class A , overriddenClassMeth( ) is converted to A.overriddenClassMeth( ) . Class A can invoke B 's version using the qualified reference B.overriddenClassMeth( ) .


[1] The same access rules apply to class properties as to class methods.

6.4.5.2 Member access from an overriding instance method

An instance method in a subclass that overrides an instance method in a superclass is known as an overriding instance method . For example, in the following code, the method over( ) in class B overrides the method of the same name in class A :

 class A {   public function over ( ):Void {   } } class B extends A {   public function over ( ):Void {   } } 

An instance of B can invoke B 's version of over( ) as follows:

 var bInstance:B = new B( ); bInstance.over( ); 

Table 6-4 describes what happens when bInstance invokes the overriding instance method over( ) , and over( ) subsequently accesses various methods and properties. Pay special attention to the Notes column, which highlights differences between member access in an overriding method and member access from an inherited method (as covered in Table 6-3).

In general terms, inherited and overridden methods have the scope of the superclass (where they are defined), whereas overriding methods have the scope of the subclass (where they are defined).

Table 6-4. Member access from an overriding instance method

Member accessed

Example

Notes

Instance method of subclass

 class A {  public function over ( ):Void {  } } class B extends A {  public function methOfB( ):Void {  }  public function over ( ):Void {   methOfB( );  // Executes   B.methOfB( )   } } 

Methods defined within a subclass can access each other, even when one is an overriding method. So, the overriding method, B.over( ) , can execute B.methOfB( ) . Compare with Table 6-3, in which invoking an instance method of a subclass from an inherited method yields an error.

Inherited instance method (defined in superclass)

 class A {  public function over ( ):Void {  }  public function otherMethOfA( ):Void {  } } class B extends A {  public function over ( ):Void {   // Invokes   otherMethOfA( )   otherMethOfA( );     } } 

As in Table 6-3, any method of the subclass ( B ), even an overriding method, can access a method defined in the superclass ( A ).

Overridden instance method (defined in both superclass and subclass)

 class A {  public function over ( ):Void {  }  public function over2 ( ):Void {  } } class B extends A {  public function over ( ):Void {   // Invokes   B.over2( )   not   A.over2( )   over2( );     }  public function over2 ( ):Void {  } } 

As in all cases, the compiler attempts to resolve unqualified references within the subclass before looking to the superclass. In this case, the overriding over( ) method invokes over2( ) , another overriding method in the same subclass.

Instance property of subclass

 class A {  public function over ( ):Void {  } } class B extends A {  public var propOfB:Number = 1;  public function over ( ):Void {   trace(propOfB);  // Displays: 1  } } 

Methods defined within a subclass can access properties defined in the same subclass. So, the overriding method, B.over( ) , can access propOfB . Compare with Table 6-3, in which accessing an instance property of a subclass from an inherited method yields an error.

Inherited instance property (defined in superclass)

 class A {  public var propOfA:Number = 2;  public function over ( ):Void {  } } class B extends A {  public function over ( ):Void {   trace(propOfA);  // Displays: 2  } } 

Methods defined within a subclass, even overriding methods, can access properties defined in the superclass. So, the overriding method, B.over( ) , can access propOfA .

Overridden instance property (defined in both superclass and subclass)

 class A {  public var overriddenProp:Number = 3;  public function over ( ):Void {  } } class B extends A {  public var overriddenProp:Number = 4;  public function over ( ):Void {   trace(overriddenProp);  // Displays: 4  } } 

The subclass method displays the subclass's value of the overridden property (4 , not 3). Because bInstance stores only one value in overriddenProp , the overriding method has no access to the superclass's overridden property value.

Class method of subclass, unqualified reference [2]

 class A {  public function over ( ):Void {  } } class B extends A {  public static function classMethOfB( ):Void {  }  public function over ( ):Void {   classMethOfB( ); // Invokes   B.classMethOfB( )   } } 

At compile time, unqualified references to class methods are resolved relative to the current class ( classMethOfB( ) , is resolved to B.classMethOfB( ) , which exists). Compare with Table 6-3, in which an unqualified reference to a static method from within an inherited method (defined in the superclass) yields an error.

Class method of subclass, qualified reference [1]

 class A {  public function over ( ):Void {  } } class B extends A {  public static function classMethOfB( ):Void {  }  public function over ( ):Void {   B.classMethOfB( );  // Executes properly  } } 

B.classMethOfB( ) is a qualified reference, so the compiler knows to look on class B for the class method (this is true for all classes even in the case of overriding methods).

Inherited class method (defined in superclass) [1]

 class A {  public static function classMethOfA( ):Void {  }  public function over ( ):Void {  } } class B extends A {  public function over ( ):Void {   classMethOfA( );  // Invokes   A.classMethOfA( )   } } 

At compile time, unqualified references to class methods are resolved relative to the current class. So classMethOfA( ) resolves B.classMethOfA( ) . Although, subclass B doesn't directly define classMethOfA( ) , that reference succeeds because an inherited class method can be accessed via any subclass, so B.classMethOfA( ) and A.classMethOfA( ) work the same in this case.

Overridden class method (defined in both superclass and subclass) [1]

 class A {  public static function overriddenClassMeth( ):Void {  }  public function over ( ):Void {   // Invokes   A.overriddenClassMeth( )   overriddenClassMeth( );  } } class B extends A {  public static function overriddenClassMeth( ):Void {  }  public function over ( ):Void {   // Invokes   B.overriddenClassMeth( )   overriddenClassMeth( );  } } // Usage: var aInstance:A = new A( ); var bInstance:B = new B( ); // Invokes A's version of overriddenClassMeth( ) aInstance.over( ); // Invokes B's version of overriddenClassMeth( ) bInstance.over( ); 

At compile time, unqualified references to class methods are resolved relative to the current class. So in B 's version of over( ) , overriddenClassMeth( ) resolves to B.overriddenClassMeth( ) . To invoke A 's version of the method, use the qualified reference A.overriddenClassMeth( ) .

Notice that when an unqualified reference is used, the version of the method invoked is that of the class that contains the method call.


[2] The same access rules apply to class properties as to class methods.

6.4.5.3 Member access from an overridden method invoked via super

A method in a superclass that is overridden by a method in a subclass is known as an overridden instance method . As we learned earlier, an overridden instance method can be invoked via the keyword super . For example, in the following code, the overridden method, over( ) , in class A is invoked via super from the overriding method of the same name in class B :

 class A {   public function over ( ):Void {   } } class B extends A {   public function over ( ):Void {     super.over( );   } } 

Note the difference between using an inherited method versus calling an overridden method via super . In the former case, the subclass does not define the method but rather implicitly executes the method inherited from the superclass. In the latter case, the subclass's overriding method must use super to explicitly call the overridden superclass method.

In the following code, we invoke the over( ) method on an instance of B . Without looking at the class definitions we can't tell that it invokes A.over( ) via super behind the scenes. In fact, in a properly designed object-oriented application, the internal implementation should be of no concern to the external code calling the class.

 var bInstance:B = new B( ); bInstance.over( ); 

Member access from an overridden method is the same as from an inherited method, as shown in Table 6-3. The following code shows you the syntactical difference between overridden method access and inherited method access. Despite the difference in code, the Notes column in Table 6-3 pertains verbatim to member access from overridden methods. See also the earlier sections "Overriding Properties" and "Overriding Instance Methods."

Here, we invoke the overridden method, over( ), via super , which in turn invokes otherMethOfA( ) :

 class A {  public function over ( ):Void {   // Executes   otherMethOfA( )   otherMethOfA( );  }  public function otherMethOfA( ):Void {  } } class B extends A {  public function over ( ):Void {   // Executes A's version of   over( )   super.over( );  } } // Usage: var bInstance:B = new B( ); bInstance.over( ); 

Here, we invoke the inherited method, methOfA( ) , which in turn invokes otherMethOfA( ) :

 // ==== Inherited method access ==== class A {  public function methOfA( ):Void {   // Executes   otherMethOfA( )       otherMethOfA( );      }  public function otherMethOfA( ):Void {  } } class B extends A { } // Usage: var bInstance:B = new B( ); bInstance.methOfA( ); 

The complexity of overridden member access in Tables Table 6-3 and Table 6-4 is, in itself, a warning to the developer not to override methods lightly.

Always use caution when overriding members. Excessive overriding can lead to unmanageable, confusing source code. If you can't easily predict the runtime results of your code's overridden member access, you should simplify your code.


Now that we've learned how member access works in inherited and overridden instance methods, let's see how it works in inherited and overridden class methods.

6.4.6 Member Access from Inherited and Overridden Class Methods

Compared to instance method member access, member access from class methods is quite restricted. A class method cannot use the keyword super to refer to members of a superclass. Neither can it refer to instance properties or instance methods.

A class method, whether overridden or not, is allowed to access other class methods and class properties only.


However, even within these restrictions, member access from a class method has the potential to be confusing. For clarity of code (and your own sanity ), you should always refer to class methods and class properties through a fully qualified class reference (e.g., SomeClass.someMethod( ) , not someMethod( ) ).

The potential for confusion lies once again in the compiler's automatic resolution of unqualified member references. Let's take a walk through this bramble, starting with a class, A , that defines a single class method, m( ) , and a single class property, p .

 class A {   public static var p:String = "Hello there";   public static function m ( ):Void {     trace(p);   } } 

As we learned earlier, at compile time, unqualified static member references (such as p ) are resolved to qualified references of the form:

   ClassName   .   memberName   

where ClassName is the name of the class that contains the member reference and memberName is the name of the member.

Hence, when the compiler compiles the preceding code for class A , it resolves the unqualified property p in method m( ) to the qualified reference: A.p . Once the compiler's work is done, the bytecode for method m( ) effectively reads:

 public static function m ( ):Void {  trace(A.p);  } 

Therefore, when we invoke m( ) through A :

 A.m( ); 

it displays the value of A.p , namely:

 Hello there 

So far, so good. Now suppose class B extends class A , adding no properties or methods of its own:

 class B extends A { } 

When we invoke m( ) through B :

 B.m( ); 

it likewise displays the value of A.p , namely:

 Hello there 

Why? Because class A was compiled exactly as before, and the unqualified reference p in the method m( ) was resolved to A.p .

Invoking m( ) through B at runtime has no impact on what has already happened at compile time.


Now things get interesting. Let's override the class property p in class B :

 class B extends A {   public static var p:String = "Goodbye"; } 

Once again, we invoke m( ) through B :

 B.m( ); 

and the output is again :

 Hello there 

What's going on here? Our class B now defines its own property, p . And the method m( ) was invoked through B . So we might think the property reference to p in the method m( ) should yield the value of B.p ("Goodbye") rather than the value of A.p ("Hello there"). As tempting as that line of thinking is, we'd be wrong. It's worth repeating: invoking m( ) through B at runtime has no impact on what has already happened at compile time . The compile-time conversion of p to A.p is permanent, and it happens regardless of our creation of B.p and our invocation of m( ) through B . While we may think that our code ambiguously reads:

 public static function m ( ):Void {   trace(p); } 

To the interpreter, it reads, unambiguously as:

 public static function m ( ):Void {  trace(A.p);  } 

Despite our best efforts, we cannot convince the interpreter that we mean B.p when we refer to the unqualified property p from within a static method of A . (You should be starting to see why qualified references to static members are so much clearer than unqualified references.)

We're almost out of the bramble.

Suppose we now override both the class method m( ) and the class property p in B :

 class B extends A {   public static var p:String = "The property B.p";   public static function m ( ):Void {     trace(p);   } } 

At compile time, the reference in B.m( ) to p resolves to: B.p . So this time if we invoke m( ) through B :

 B.m( ); 

we're invoking B 's version of the method, so the output is:

 Goodbye 

And if we invoke m( ) through A :

 A.m( ); 

we're invoking A 's version of the method, so the output is:

 Hello there 

Finally, we have what we wanted. Each class method displays the class property defined within its class.

The moral of the story is "Always qualify your static member references."


Hopefully this little journey through the bramble will help us avoid similar thorns in our code.

6.4.7 Best Practices for Overriding

So far, our consideration of overriding in ActionScript 2.0 has been limited to "like members." That is, we've seen how an instance method can override another instance method or how a class property can override another class property. But we haven't discussed what happens when, say, an instance property overrides a class method. We've specifically avoided this topic, because even though ActionScript 2.0 allows a property to override a method, doing so is atrocious OOP form.

In fact, ActionScript does not prevent any kind of overriding. If a member in a subclass has the same name as a member in its superclass, the subclass's version overrides the superclass version. If the two members are both methods, they can have any number of parameters, any types of parameters, any return type, or any access level ( public or private ). If one member is a class member, the other can be an instance member. If one member is a property, the other can be a method. As long as the names of the members match, the override is legal. This extremely lenient behavior is a departure from the ECMA specification and should not be relied on.

For guidance on what constitutes best practices for overriding, we'll start with Java's rules. Note that, in Java, "properties" are called "variables," and for technical reasons, only instance methods are said to "override" each other. Variables and class methods are said to "hide" (not "override") each other. However, to maintain our ActionScript perspective in the following discussion, we'll use ActionScript's terminology even though we're describing Java's rules.

In Java, a legal override of a method must obey these rules:

  • Both methods must be instance methods, or both methods must be class methods. An instance method cannot override a class method and vice versa.

  • Both methods must have the same name.

  • Both methods must have the same number of parameters. (ActionScript 2.0 developers must sometimes ignore this rule; see the later explanation.)

  • The parameters of both methods must have the same datatypes. (ActionScript 2.0 developers must sometimes ignore this rule; see the later explanation.)

  • Both methods must have the same return type.

  • The overriding method must be at least as accessible as the overridden method (that is, the overriding method can't be private if the overridden method is public ).

In Java, an instance property can override an instance property or a class property. The reverse is also true ”a class property can override an instance property or a class property. However, as noted earlier, it's clearest to override instance properties only with other instance properties and class properties only with other class properties.

Table 6-5 summarizes Java's member overriding rules. When programming in ActionScript, it's wise to follow them, with one exception: in ActionScript, it is legitimate to change the number, datatype(s), and order of parameters for a method when overriding it. The method doing the overriding might need to offer different parameters in order to handle a specialized situation. In Java, defining a subclass method with the same name as a superclass method, but with a different number, datatype(s), or order of parameters constitutes a method overload , not a method override . That is, in Java, rather than obscuring the superclass's method, the subclass's version of the method coexists with the superclass's version. ActionScript does not support overloading, so the incongruent-parameter override is considered acceptable.

Table 6-5. Overriding members in Java

Member overridden

Overriding member is an instance method

Overriding member is a class method

Overriding member is an instance property

Overriding member is a class property

Instance method

Legal, with restrictions listed earlier

Illegal

Coexistence [3]

Coexistence [3]

Class method

Illegal

Legal, with restrictions listed earlier

Coexistence [3]

Coexistence [3]

Instance property

Coexistence [3]

Coexistence [3]

Legal

Legal

Class property

Coexistence [3]

Coexistence [3]

Legal

Legal


[3] In Java, there is effectively no override because the property and method of the same name can coexist. ActionScript does not allow a property and a method in the same class to have the same name. This form of member overriding should be avoided in ActionScript.

 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net