Inheritance



Inheritance is a form of code reuse that allows programmers to develop new classes that are based on existing classes. The existing classes are often referred to as base classes or superclasses, while the new classes are usually called subclasses. A key advantage of inheritance is that it allows you to reuse code from a base class yet leave the existing code unmodified. Moreover, inheritance requires no changes to the way that other classes interact with the base class. Rather than modifying an existing class that may have been thoroughly tested or may already be in use, using inheritance you can treat that class as an integrated module that you can extend with additional properties or methods. Accordingly, you use the extends keyword to indicate that a class inherits from another class.

Inheritance also allows you to take advantage of polymorphism in your code. Polymorphism is the ability to use a single method name for a method that behaves differently when applied to different data types. A simple example is a base class named Shape with two subclasses named Circle and Square. The Shape class defines a method named area(), which returns the area of the shape. If polymorphism is implemented, you can call the area() method on objects of type Circle and Square and have the correct calculations done for you. Inheritance enables polymorphism by allowing subclasses to inherit and redefine, or override, methods from the base class. In the following example, the area() method is redefined by the Circle and Square classes:

class Shape 
{ 
    public function area():Number 
    { 
        return NaN; 
    } 
} 
 
class Circle extends Shape 
{ 
    private var radius:Number = 1; 
    override public function area():Number 
    { 
        return (Math.PI * (radius * radius)); 
    } 
} 
 
class Square extends Shape 
{ 
    private var side:Number = 1; 
    override public function area():Number 
    { 
        return (side * side); 
    } 
} 
 
var cir:Circle = new Circle(); 
trace(cir.area()); // output: 3.141592653589793 
var sq:Square = new Square(); 
trace(sq.area()); // output: 1

Because each class defines a data type, the use of inheritance creates a special relationship between a base class and a class that extends it. A subclass is guaranteed to possess all the properties of its base class, which means that an instance of a subclass can always be substituted for an instance of the base class. For example, if a method defines a parameter of type Shape, it is legal to pass an argument of type Circle because Circle extends Shape, as in the following:

function draw(shapeToDraw:Shape) {} 
 
var myCircle:Circle = new Circle(); 
draw(myCircle);

Instance properties and inheritance

An instance property, whether defined with the function, var, or const keywords, is inherited by all subclasses as long as the property is not declared with the private attribute in the base class. For example, the Event class in ActionScript 3.0 has a number of subclasses that inherit properties common to all event objects.

For some types of events, the Event class contains all the properties necessary to define the event. These types of events do not require instance properties beyond those defined in the Event class. Examples of such events are the complete event, which occurs when data has loaded successfully, and the connect event, which occurs when a network connection has been established.

The following example is an excerpt from the Event class that shows some of the properties and methods that are inherited by subclasses. Because the properties are inherited, an instance of any subclass can access these properties.

public class Event 
{ 
    public function get type():String; 
    public function get bubbles():Boolean; 
    ... 
 
    public function stopPropagation():void {} 
    public function stopImmediatePropagation():void {} 
    public function preventDefault():void {} 
    public function isDefaultPrevented():Boolean {} 
    ... 
}

Other types of events require unique properties not available in the Event class. These events are defined using subclasses of the Event class so that new properties can be added to the properties defined in the Event class. An example of such a subclass is the MouseEvent class, which adds properties unique to events associated with mouse movement or mouse clicks, such as the mouseMove and click events. The following example is an excerpt from the MouseEvent class that shows the definition of properties that exist on the subclass but not on the base class:

public class MouseEvent extends Event 
{ 
    public static const CLICK:String= "click"; 
    public static const MOUSE_MOVE:String = "mouseMove"; 
    ... 
 
    public function get stageX():Number {} 
    public function get stageY():Number {} 
    ... 
}

Access control specifiers and inheritance

If a property is declared with the public keyword, the property is visible to code anywhere. This means that the public keyword, unlike the private, protected, and internal keywords, places no restrictions on property inheritance.

If a property is declared with private keyword, it is visible only in the class that defines it, which means that it is not inherited by any subclasses. This behavior is different from previous versions of ActionScript, where the private keyword behaved more like the ActionScript 3.0 protected keyword.

The protected keyword indicates that a property is visible not only within the class that defines it, but also to all subclasses. Unlike the protected keyword in the Java programming language, the protected keyword in ActionScript 3.0 does not make a property visible to all other classes in the same package. In ActionScript 3.0, only subclasses can access a property declared with the protected keyword. Moreover, a protected property is visible to a subclass whether the subclass is in the same package as the base class or in a different package.

To limit the visibility of a property to the package in which it is defined, use the internal keyword or do not use any access control specifier. The internal access control specifier is the default access control specifier that applies when one is not specified. A property marked as internal will be inherited only by a subclass that resides in the same package.

You can use the following example to see how each of the access control specifiers affects inheritance across package boundaries. The following code defines a main application class named AccessControl and two other classes named Base and Extender. The Base class is in a package named foo and the Extender class, which is a subclass of the Base class, is in a package named bar. The AccessControl class imports only the Extender class and creates an instance of Extender that attempts to access a variable named str that is defined in the Base class. The str variable is declared as public so that the code compiles and runs as shown in the following excerpt:

// Base.as in a folder named foo 
package foo 
{ 
    public class Base 
    { 
        public var str:String = "hello"; // change public on this line 
    } 
} 
 
// Extender.as in a folder named bar 
package bar 
{ 
    import foo.Base; 
    public class Extender extends Base 
    { 
        public function getString():String { 
            return str; 
        } 
    } 
} 
 
// main application class in file named AccessControl.as 
package 
{ 
    import flash.display.MovieClip; 
    import bar.Extender; 
    public class AccessControl extends MovieClip 
    { 
        public function AccessControl() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.str);// error if str is not public 
            trace(myExt.getString()); // error if str is private or internal 
        } 
    } 
}

To see how the other access control specifiers affect compilation and execution of the preceding example, change the str variable’s access control specifier to private, protected, or internal after deleting or commenting out the following line from the AccessControl class:

trace(myExt.str);// error if str is not public

Overriding variables not permitted

Properties that are declared with the var or const keywords are inherited but cannot be overridden. To override a property means to redefine the property in a subclass. The only type of property that can be overridden are methods—that is, properties declared with the function keyword. Although you cannot override an instance variable, you can achieve similar functionality by creating getter and setter methods for the instance variable and overriding the methods. For more information, see Overriding methods.

Overriding methods

To override a method means to redefine the behavior of an inherited method. Static methods are not inherited and cannot be overridden. Instance methods, however, are inherited by subclasses and can be overridden as long as the following two criteria are met:

  • The instance method is not declared with the final keyword in the base class. When used with an instance method, the final keyword indicates the programmer’s intent to prevent subclasses from overriding the method.

  • The instance method is not declared with the private access control specifier in the base class. If a method is marked as private in the base class, there is no need to use the override keyword when defining an identically named method in the subclass, because the base class method will not be visible to the subclass.

    To override an instance method that meets these criteria, the method definition in the subclass must use the override keyword and must match the superclass version of the method in the following ways:

  • The override method must have the same level of access control as the base class method. Methods marked as internal have the same level of access control as methods that have no access control specifier.

  • The override method must have the same number of parameters as the base class method.

  • The override method parameters must have the same data type annotations as the parameters in the base class method.

  • The override method must have the same return type as the base class method.

The names of the parameters in the override method, however, do not have to match the names of the parameters in the base class, as long as the number of parameters and the data type of each parameter matches.

The super statement

When overriding a method, programmers often want to add to the behavior of the superclass method they are overriding instead of completely replacing the behavior. This requires a mechanism that allows a method in a subclass to call the superclass version of itself. The super statement provides such a mechanism, in that it contains a reference to the immediate superclass. The following example defines a class named Base that contains a method named thanks() and a subclass of the Base class named Extender that overrides the thanks() method. The Extender.thanks() method uses the super statement to call Base.thanks().

package { 
    import flash.display.MovieClip; 
    public class SuperExample extends MovieClip 
    { 
        public function SuperExample() 
        { 
            var myExt:Extender = new Extender() 
            trace(myExt.thanks()); // output: Mahalo nui loa 
        } 
    } 
} 
 
class Base { 
    public function thanks():String 
    { 
        return "Mahalo"; 
    } 
} 
 
class Extender extends Base 
{ 
    override public function thanks():String 
    { 
        return super.thanks() + " nui loa"; 
    } 
}

Overriding getters and setters

Although you cannot override variables defined in a superclass, you can override getters and setters. For example, the following code overrides a getter named currentLabel that is defined in the MovieClip class in ActionScript 3.0.:

package 
{ 
    import flash.display.MovieClip; 
    public class OverrideExample extends MovieClip 
    { 
        public function OverrideExample() 
        { 
            trace(currentLabel) 
        } 
        override public function get currentLabel():String 
        { 
            var str:String = "Override: "; 
            str += super.currentLabel; 
            return str; 
        } 
    } 
}

The output of the trace() statement in the OverrideExample class constructor is Override: null, which shows that the example was able to override the inherited currentLabel property.

Static properties not inherited

Static properties are not inherited by subclasses. This means that static properties cannot be accessed through an instance of a subclass. A static property can be accessed only through the class object on which it is defined. For example, the following code defines a base class named Base and a subclass that extends Base named Extender. A static variable named test is defined in the Base class. The code as written in the following excerpt does not compile in strict mode and generates a run-time error in standard mode.

package { 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// error 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base { }

The only way to access the static variable test is through the class object, as shown in the following code:

Base.test;

It is permissible, however, to define an instance property using the same name as a static property. Such an instance property can be defined in the same class as the static property or in a subclass. For example, the Base class in the preceding example could have an instance property named test. The following code compiles and executes because the instance property is inherited by the Extender class. The code would also compile and execute if the definition of the test instance variable is moved, but not copied, to the Extender class.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
            trace(myExt.test);// output: instance 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
    public var test:String = "instance"; 
} 
 
class Extender extends Base {}

Static properties and the scope chain

Although static properties are not inherited, they are within the scope chain of the class that defines them and any subclass of that class. As such, static properties are said to be in scope of both the class in which they are defined and any subclasses. This means that a static property is directly accessible within the body of the class that defines the static property and any subclass of that class.

The following example modifies the classes defined in the previous example to show that the static test variable defined in the Base class is in scope of the Extender class. In other words, the Extender class can access the static test variable without prefixing the variable with the name of the class that defines test.

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base { 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public function Extender() 
    { 
        trace(test); // output: static 
    } 
     
}

If an instance property is defined that uses the same name as a static property in the same class or a superclass, the instance property has higher precedence in the scope chain. The instance property is said to shadow the static property, which means that the value of the instance property is used instead of the value of the static property. For example, the following code shows that if the Extender class defines an instance variable named test, the trace() statement uses the value of the instance variable instead of the value of the static variable.:

package 
{ 
    import flash.display.MovieClip; 
    public class StaticExample extends MovieClip 
    { 
        public function StaticExample() 
        { 
            var myExt:Extender = new Extender(); 
        } 
    } 
} 
 
class Base 
{ 
    public static var test:String = "static"; 
} 
 
class Extender extends Base 
{ 
    public var test:String = "instance"; 
    public function Extender() 
    { 
        trace(test); // output: instance 
    } 
     
}