繼承

繼承是一種重複使用程式碼的形式,可以讓程式設計人員根據現有的類別開發新類別。現有的類別通常稱為「基底類別」或「父類別」,而新類別則稱為「子類別」。繼承的主要優點是可以讓您重複使用基底類別的程式碼,但是讓現有程式碼保持原狀不變;而且,繼承不需要變更其它類別與基底類別互動的方式。您不必修改已經過徹底測試或可能已經在使用的現有類別而使用繼承,而可以將該類別視為可用其它屬性或方法擴充的整合式模組。因此,要使用 extends 關鍵字,指出類別是從另一個類別繼承而來。

繼承也讓您在程式碼中發揮利用「多型」。多型能夠讓方法使用單一方法名稱,而在套用至不同資料類型時,表現出不同的行為方式。簡單的範例是名為 Shape 的基底類別,具有兩個名為 Circle 和 Square 的子類別。Shape 類別會定義名為 area() 的方法,傳回形狀的面積。若已實作多型,您可以在 Circle 和 Square 類型的物件上呼叫 area() 方法,為您完成正確的計算。繼承可以透過允許子類別從基底類別繼承及重新定義或「覆寫」方法,啟用多型。在下列範例中, area() 方法是由 Circle 和 Square 類別重新定義:

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 類型的參數,傳遞 Circle 類型的引數是合法的,因為 Circle 擴充 Shape,如下所示:

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 關鍵字宣告的屬性。而且,不管子類別是在與基底類別相同的套件或是不同的套件中,保護的屬性都可讓子類別看見。

若要限制屬性在定義該屬性的套件中之可見性,請使用 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 關鍵字宣告的屬性可以繼承,但不能加以覆寫。所謂覆寫屬性,就是要在子類別中重新定義屬性。唯一可以覆寫的屬性類型,是取得及設定存取子 (使用 function 關鍵字宣告的屬性)。雖然您不能覆寫實體變數,但是您可以透過為實體變數建立 getter 和 setter 方法,然後覆寫方法,達到類似的功能。

覆寫方法

所謂覆寫方法,就是要重新定義繼承的方法之行為方式。靜態方法不是繼承的,所以不能覆寫。但是實體方法是由子類別加以繼承,只要符合下列兩項條件,即可進行覆寫:

  • 實體方法不用 final 關鍵字在基底類別中宣告。 final 關鍵字配合實體方法使用時,表示程式設計人員意圖阻止子類別覆寫方法。

  • 實體方法不會使用 private 存取控制指定字在基底類別中宣告。如果方法在基底類別中標記為 private ,就不需要在子類別定義名稱完全相同的方法時,使用 override 關鍵字,因為子類別無法看見基底類別方法。

    若要覆寫符合這些條件的實體方法,子類別中的方法定義必須使用 override 關鍵字,也必須在下列各方面與方法的父類別版本相符:

  • 覆寫方法必須具有與基底類別方法相同的存取控制層級。標示為 internal 的方法必須具有與無存取控制指定字的方法相同之存取控制層級。

  • 覆寫方法必須具有與基底類別方法相同的參數數目。

  • 覆寫方法參數必須具有與基底類別方法中參數相同的資料類型。

  • 覆寫方法必須具有與基底類別方法相同的傳回類型。

但是,在覆寫方法中的參數名稱不必與基底類別中參數名稱相同,只要各參數的參數數目及資料類型相符即可。

super 陳述式

覆寫方法時,程式設計人員經常是要新增所要覆寫父類別方法的行為方式,而不是完全取代其行為方式。這需要一種機制,可以讓子類別中的方法呼叫本身的父類別版, super 陳述式就會提供這種機制,因此其中包含直屬父類別的參考。下列範例會定義名為 Base 的類別,其中包含名為 thanks() 的方法,以及名為 Extender 而覆寫 thanks() 方法的 Base 類別之子類別。 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。例如,下列程式碼會覆寫名為 currentLabel 的 getter,它定義於 ActionScript 3.0 的 MovieClip 類別中:

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 的基底類別,以及擴充 Base 而名為 Extender 的子類別;名為 test 的靜態變數是定義於 Base 類別中。如下列摘錄中所撰寫的程式碼在嚴謹模式中不會進行編譯,而在標準模式中會產生執行階段錯誤。

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