继承

继承是指一种代码重用的形式,允许程序员基于现有类开发新类。现有类通常称为 基类 超类 ,而新类称为 子类 。继承的主要优势是,允许重复使用基类中的代码,但不修改现有代码。此外,继承不要求改变其他类与基类交互的方式。不必修改可能已经过彻底测试或可能已被使用的现有类,使用继承可将该类视为一个集成模块,可使用其他属性或方法对它进行扩展。因此,您使用 extends 关键字指明类从另一类继承。

通过继承还可以在代码中利用 多态 。有一种方法在应用于不同数据类型时会有不同行为,多态就是对这样的方法应用一个方法名的能力。名为 Shape 的基类就是一个简单的示例,该类有名为 Circle 和 Square 的两个子类。Shape 类定义了名为 area() 的方法,该方法返回形状的面积。如果已实现多态,则可以对 Circle 和 Square 类型的对象调用 area() 方法,然后执行正确的计算。使用继承能实现多态,实现的方式是允许子类继承和重新定义或 覆盖 基类中的方法。在下面的示例中,由 Circle 和 Square 两个类重新定义了 area() 方法:

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

因为每个类定义一个数据类型,所以使用继承会在基类和扩展基类的类之间创建一个特殊关系。子类保证拥有其基类的所有属性,这意味着子类的实例总是可以替换基类的实例。例如,如果方法定义了 Shape 类型的参数 (parameter),由于 Circle 扩展了 Shape,因此 Circle 类型的参数 (argument) 是合法的,如下所示:

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

实例属性和继承

对于实例属性,无论是使用 function var ,还是 const 关键字定义,只要在基类中未使用 private 属性进行声明,就可以由子类继承。例如,ActionScript 3.0 中的 Event 类具有许多子类,它们继承了所有事件对象共有的属性。

对于某些类型的事件,Event 类包含了定义事件所需的所有属性。这些类型的事件不需要 Event 类中定义的实例属性以外的实例属性。 complete 事件和 connect 事件就是这样的事件,前者在成功加载数据时发生,后者在建立网络连接时发生。

下面的示例是从 Event 类中摘录的,显示由子类继承的某些属性和方法。由于继承了属性,因此任何子类的实例都可以访问这些属性。

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 {} 
    ... 
}

其他类型的事件需要 Event 类中没有提供的特有属性。这些事件是使用 Event 类的子类定义的,所以可向 Event 类中定义的属性添加新属性。MouseEvent 类就是这样的一个子类,它可添加与鼠标移动或鼠标单击相关的事件的特有属性,如 mouseMove click 事件。下面的示例是从 MouseEvent 类中摘录的,它说明了在子类中存在但在基类中不存在的属性的定义:

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 {} 
    ... 
}

访问控制说明符和继承

如果某一属性是用 public 关键字声明的,则该属性对任何位置的代码可见。这表示 public 关键字与 private protected internal 关键字不同,它对属性继承没有任何限制。

如果属性是使用 private 关键字声明的,该属性只在定义该属性的类中可见,这表示它不能由任何子类继承。此行为与以前版本的 ActionScript 不同,在这些版本中, private 关键字的行为更类似于 ActionScript 3.0 中的 protected 关键字。

protected 关键字指出某一属性不仅在定义该属性的类中可见,而且还在所有子类中可见。与 Java 编程语言中的 protected 关键字不一样,ActionScript 3.0 中的 protected 关键字并不使属性对同一包中的所有其他类可见。在 ActionScript 3.0 中,只有子类可以访问使用 protected 关键字声明的属性。此外,protected 属性对子类可见,不管子类和基类是在同一包中,还是在不同包中。

要限制某一属性在定义该属性的包中的可见性,请使用 internal 关键字或者不使用任何访问控制说明符。未指定访问控制说明符时,应用的默认访问控制说明符是 internal 访问控制说明符。标记为 internal 的属性仅由位于同一包中的子类继承。

可以使用下面的示例来查看每一个访问控制说明符如何影响跨越包边界的继承。以下代码定义了一个名为 AccessControl 的主应用程序类和两个名为 Base 的 Extender 的其他类。Base 类在名为 foo 的包中,Extender 类是 Base 类的子类,它在名为 bar 的包中。AccessControl 类只导入 Extender 类,并创建一个 Extender 实例,该实例试图访问在 Base 类中定义的名为 str 的变量。 str 变量将声明为 public 以使代码能够进行编译和运行,如以下摘录中所示:

// 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 
        } 
    } 
}

要查看其他访问控制说明符如何影响先前示例的编译和执行,请在 AccessControl 类中删除或注释掉以下行,然后将 str 变量的访问控制说明符更改为 private protected internal

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

不允许覆盖变量

将继承使用 var const 关键字声明的属性,但不能对其进行覆盖。覆盖某一属性就表示在子类中重新定义该属性。唯一可覆盖的属性类型是 get 和 set 取值函数(使用 function 关键字声明的属性)。虽然不能覆盖实例变量,但是通过为实例变量创建 getter 和 setter 方法并覆盖这些方法,可实现类似的功能。

覆盖方法

覆盖方法表示重新定义已继承方法的行为。静态方法不能继承,也不能覆盖。但是,实例方法可由子类继承,也可覆盖,只要符合以下两个条件:

  • 实例方法在基类中不是使用 final 关键字声明的。当 final 关键字与实例方法一起使用时,该关键字指明程序员的设计目的是要禁止子类覆盖方法。

  • 实例方法在基类中不是使用 private 访问控制说明符声明的。如果某个方法在基类中标记为 private ,则在子类中定义同名方法时不需要使用 override 关键字,因为基类方法在子类中不可见。

    要覆盖符合这些条件的实例方法,子类中的方法定义必须使用 override 关键字,且必须在以下几个方面与方法的超类版本相匹配:

  • 覆盖方法必须与基类方法具有相同级别的访问控制。标记为内部的方法与没有访问控制说明符的方法具有相同级别的访问控制。

  • 覆盖方法必须与基类方法具有相同的参数数。

  • 覆盖方法参数必须与基类方法参数具有相同的数据类型注释。

  • 覆盖方法必须与基类方法具有相同的返回类型。

但是,覆盖方法中的参数名不必与基类中的参数名相匹配,只要参数数和每个参数的数据类相匹配即可。

super 语句

覆盖方法时,程序员经常希望在要覆盖的超类方法的行为上添加行为,而不是完全替换该行为。这需要通过某种机制来允许子类中的方法调用它本身的超类版本。 super 语句就提供了这样一种机制,其中包含对直接超类的引用。下面的示例定义了名为 Base 的类(其中包含名为 thanks() 的方法),还包含名为 Extender 的 Base 类的子类(用于覆盖 thanks() 方法)。 Extender.thanks() 方法使用 super 语句调用 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"; 
    } 
}

覆盖 getter 和 setter

虽然不能覆盖超类中定义的变量,但是可以覆盖 getter 和 setter。例如,下面的代码用于覆盖在 ActionScript 3.0 的 MovieClip 类中定义的名为 currentLabel 的 getter:

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; 
        } 
    } 
}

OverrideExample 类构造函数中的 trace() 语句的输出是 Override: null ,这说明该示例能够覆盖继承的 currentLabel 属性。

不继承静态属性

静态属性不由子类继承。这意味着不能通过子类的实例访问静态属性。只能通过定义静态属性的类对象来访问静态属性。例如,以下代码定义了名为 Base 的基类和扩展名为 Extender 的 Base 子类。Base 类中定义了名为 test 的静态变量。以下摘录中编写的代码在严格模式下不会进行编译,在标准模式下会生成运行时错误。

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 { }

访问静态变量 test 的唯一方式是通过类对象,如以下代码所示:

Base.test;

但允许使用与静态属性相同的名称定义实例属性。可以在与静态属性相同的类中或在子类中定义这样的实例属性。例如,以上示例中的 Base 类可以具有一个名为 test 的实例属性。在以下代码中,由于 Extender 类继承了实例属性,因此代码能够进行编译和执行。如果 test 实例变量的定义移到(不是复制)Extender 类,该代码也会编译并执行。

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 {}

静态属性和作用域链

虽然并不继承静态属性,但是静态属性在定义它们的类或该类的任何子类的作用域链中。同样,可以认为静态属性在定义它们的类和任何子类的 作用域 中。这意味着在定义静态属性的类体及该类的任何子类中可直接访问静态属性。

下面的示例对上一个示例中定义的类进行了修改,以说明 Base 类中定义的 test 静态变量在 Extender 类的作用域中。换句话说, Extender 类可以访问 test 静态变量,而不必用定义 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 
    } 
     
}

如果使用与同类或超类中的静态属性相同的名称定义实例属性,则实例属性在作用域链中的优先级比较高。因此认为实例属性 遮蔽 了静态属性,这意味着会使用实例属性的值,而不使用静态属性的值。例如,以下代码显示如果 Extender 类定义名为 test 的实例变量, trace() 语句将使用实例变量的值,而不使用静态变量的值:

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 
    } 
     
}