Vererbung

Vererbung ist eine Form der Wiederverwendung von Code, mit der Programmierer neue Klassen entwickeln können, die auf bereits bestehenden Klassen basieren. Die bestehenden Klassen werden häufig als Basisklassen oder übergeordnete Klassen (Englisch „superclasses“) bezeichnet, während die neuen Klassen häufig als Unterklassen (Englisch „subclasses“) bezeichnet werden. Ein wesentlicher Vorteil der Vererbung besteht darin, dass Sie Code aus einer Basisklasse wiederverwenden können, ohne die vorhandene Klasse zu modifizieren. Darüber hinaus erfordert die Vererbung keine Änderungen der Art und Weise, wie andere Klassen mit der Basisklasse interagieren. Anstatt eine sorgfältig getestete oder eventuell sogar eingesetzte vorhandene Klasse zu modifizieren, können Sie eine Klasse mit der Vererbung als integriertes Modul behandeln, das Sie um zusätzliche Eigenschaften oder Methoden erweitern können. Entsprechend verwenden Sie das Schlüsselwort extends, um anzugeben, dass eine Klasse von einer anderen Klasse erbt.

Mit der Vererbung können Sie sogar von den Vorteilen des Polymorphismus in Ihrem Code profitieren. Unter Polymorphismus versteht man die Möglichkeit, einen einzelnen Methodennamen für eine Methode zu verwenden, die sich je nach Datentyp, für den sie angewendet wird, anders verhält. Ein einfaches Beispiel ist eine Basisklasse mit der Bezeichnung „Shape“ mit zwei Unterklassen namens „Circle“ und „Square“. Die Shape-Klasse definiert eine Methode namens area(), welche die Fläche der Form zurückgibt. Ist Polymorphismus implementiert, können Sie die Methode area() für die Objekte „Circle“ und „Square“ aufrufen, und es werden die richtigen Berechnungen für Sie durchgeführt. Die Vererbung ermöglicht Polymorphismus, indem sie Unterklassen das Erben und Neudefinieren bzw. Überschreiben (Englisch „override“) von Methoden aus der Basisklasse gestattet. Im folgenden Beispielcode wird die Methode area() von den Klassen „Circle“ und „Square“ neu definiert:

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

Da jede Klasse einen Datentyp definiert, sorgt die Vererbung für eine besondere Beziehung zwischen einer Basisklasse und einer Klasse, die sie erweitert. Eine Unterklasse besitzt garantiert alle Eigenschaften ihrer Basisklasse. Dies bedeutet, dass die Instanz einer Unterklasse immer als Ersatz für eine Instanz der Basisklasse verwendet werden kann. Wenn eine Methode z. B. einen Parameter des Typs „Shape“ definiert, ist es zulässig, einen Parameter des Typs „Circle“ zu übergeben, da „Circle“ den Parameter „Shape“ erweitert. Dies wird im folgenden Beispiel gezeigt:

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

Instanzeigenschaften und Vererbung

Eine Instanzeigenschaft wird von allen Unterklassen geerbt, unabhängig davon, ob sie mit dem Schlüsselwort function, var oder const definiert wurde, solange die Eigenschaft in der Basisklasse nicht mit dem Attribut private deklariert wurde. Beispielsweise hat die Event-Klasse in ActionScript 3.0 eine Reihe von Unterklassen, die Eigenschaften erben, die alle Ereignisobjekte gemeinsam haben.

Für einige Ereignistypen enthält die Event-Klasse alle Eigenschaften, die zur Definition des Ereignisses erforderlich sind. Diese Ereignistypen benötigen keine Instanzeigenschaften über die Eigenschaften hinaus, die in der Event-Klasse definiert sind. Beispiele dieser Ereignisse sind complete, das nach dem erfolgreichen Laden von Daten eintritt, und connect, das nach dem erfolgreichen Herstellen einer Netzwerkverbindung eintritt.

Das folgende Beispiel ist ein Auszug der Event-Klasse, in dem einige der Eigenschaften und Methoden gezeigt werden, die von Unterklassen geerbt werden. Da diese Eigenschaften geerbt sind, kann jede Instanz einer Unterklasse darauf zugreifen.

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

Andere Ereignistypen erfordern eindeutige Ereignisse, die in der Event-Klasse nicht zur Verfügung stehen. Diese Ereignisse werden mit Unterklassen der Event-Klasse definiert, sodass den bereits in der Event-Klasse definierten Eigenschaften neue hinzugefügt werden können. Ein Beispiel einer solchen Unterklasse ist die MouseEvent-Klasse, die Ereignisse hinzufügt, die nur für Mausbewegungen oder Mausklicks gelten, z. B. die Ereignisse mouseMove und click. Das folgende Beispiel ist ein Auszug der MouseEvent-Klasse, in dem die Definition der Eigenschaften dargestellt ist, die in der Unterklasse, jedoch nicht in der Basisklasse vorhanden sind:

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

Zugriffskontrollbezeichner und Vererbung

Eine mit dem Schlüsselwort public deklarierte Eigenschaft ist für jeden Code sichtbar. Dies bedeutet, dass das Schlüsselwort public im Gegensatz zu den Schlüsselwörtern private, protected und internal keinerlei Einschränkungen bei der Eigenschaftenvererbung einführt.

Eine mit dem Schlüsselwort private deklarierte Eigenschaft ist nur in der Klasse sichtbar, in der sie definiert wurde. Dies bedeutet, dass sie nicht von Unterklassen geerbt werden kann. Dieses Verhalten weicht von dem in früheren ActionScript-Versionen ab, in denen sich das Schlüsselwort private mehr wie das ActionScript 3.0-Schlüsselwort protected verhielt.

Das Schlüsselwort protected kennzeichnet, dass eine Eigenschaft nicht nur für die Klasse sichtbar ist, in der sie definiert wurde, sondern für alle Unterklassen. Im Gegensatz zum Schlüsselwort protected in der Programmiersprache Java macht das Schlüsselwort protected in ActionScript 3.0 eine Eigenschaft nicht für alle Klassen im gleichen Paket sichtbar. In ActionScript 3.0 können nur Unterklassen auf eine Eigenschaft zugreifen, die mit dem Schlüsselwort protected deklariert wurde. Darüber hinaus ist eine geschützte Eigenschaft für eine Unterklasse sichtbar, unabhängig davon, ob sich die Unterklasse im gleichen Paket wie die Basisklasse oder in einem anderen Paket befindet.

Um die Sichtbarkeit einer Eigenschaft auf das Paket zu beschränken, in dem sie definiert wurde, verwenden Sie entweder das Schlüsselwort internal oder wenden gar keinen Zugriffskontrollbezeichner an. Der Zugriffskontrollbezeichner internal ist der Standard-Zugriffskontrollbezeichner. Er wird automatisch angewendet, wenn kein Zugriffskontrollbezeichner angegeben wurde. Eine als internal deklarierte Eigenschaft wird nur von einer Unterklasse geerbt, die sich im gleichen Paket befindet.

Im folgenden Beispielcode können Sie sehen, wie sich die Zugriffskontrollbezeichner auf die Vererbung über Paketgrenzen hinaus auswirken. Im folgenden Beispielcode werden eine Hauptanwendungsklasse namens „AccessControl“ sowie zwei weitere Klassen „Base“ und „Extender“ definiert. Die Base-Klasse befindet sich in einem Paket namens „foo“ und die Extender-Klasse (bei der es sich um eine Unterklasse der Base-Klasse handelt) in einem Paket namens „bar“. Die AccessControl-Klasse importiert nur die Extender-Klasse und erstellt dann eine Instanz der Extender-Klasse, die versucht, auf eine Variable namens str zuzugreifen, die in der Base-Klasse definiert ist. Die Variable str ist als public deklariert, sodass der Code wie im folgenden Codeauszug kompiliert und ausgeführt wird:

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

Um festzustellen, wie sich andere Zugriffskontrollbezeichner auf die Kompilierung und Ausführung des vorangegangenen Beispiels auswirken, ändern Sie den Zugriffskontrollbezeichner der Variablen str in private, protected oder internal, nachdem Sie die folgende Zeile der AccessControl-Klasse gelöscht oder auskommentiert haben:

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

Überschreiben von Variablen nicht gestattet

Mit den Schlüsselwörtern var oder const deklarierte Eigenschaften sind geerbt, können jedoch nicht überschrieben werden. Das Überschreiben einer Eigenschaft bedeutet, dass die Eigenschaft in einer Unterklasse neu definiert wird. Der einzige Eigenschaftstyp, der überschrieben werden kann, sind get- und set-Accessorfunktionen (also Eigenschaften, die mit dem function-Schlüsselwort deklariert werden). Eine Instanzvariable kann nicht überschrieben werden. Sie können jedoch durch Erstellen von get- und set-Methoden für die Instanzvariable und Überschreiben der Methoden eine ähnliche Funktion erreichen.

Überschreiben von Methoden

Überschreiben einer Methode bedeutet, das Verhalten einer geerbten Methode neu zu definieren. Statische Methoden werden nicht geerbt und können nicht überschrieben werden. Andererseits werden die Instanzmethoden von Unterklassen geerbt und können überschrieben werden, solange die beiden folgenden Kriterien erfüllt sind:

  • Die Instanzmethode wurde in der Basisklasse nicht mit dem Schlüsselwort final deklariert. Wurde das Schlüsselwort final mit einer Instanzmethode verwendet, möchte der Programmierer verhindern, dass die Methode von Unterklassen überschrieben wird.

  • Die Instanzmethode wurde in der Basisklasse nicht mit dem Zugriffskontrollbezeichner private deklariert. Wurde eine Methode in der Basisklasse als private deklariert, muss bei der Definition einer Methode gleichen Namens in der Unterklasse das override-Schlüsselwort nicht verwendet werden, da die Basisklassenmethode für die Unterklasse nicht sichtbar ist.

    Um eine Instanzmethode zu überschreiben, die diese Kriterien erfüllt, muss die Methodendefinition in der Unterklasse das Schlüsselwort override verwenden und der Methode in der übergeordneten Klasse hinsichtlich Folgendem entsprechen:

  • Die überschreibende Methode muss über die gleiche Zugriffskontrollebene wie die Basisklassenmethode verfügen. Als „internal“ deklarierte Methoden haben die gleiche Zugriffskontrollebene wie Methoden, die über keinen Zugriffskontrollbezeichner verfügen.

  • Die überschreibende Methode muss über die gleiche Anzahl an Parametern wie die Basisklassenmethode verfügen.

  • Die Parameter der überschreibenden Methode müssen die gleichen Datentypanmerkungen wie die Parameter der Basisklassenmethode aufweisen.

  • Die überschreibende Methode muss über den gleichen Rückgabetyp wie die Basisklassenmethode verfügen.

Die Namen der Parameter in der überschreibenden Methode müssen jedoch nicht mit den Namen der Parameter in der Basisklasse übereinstimmen, solange die Anzahl der Parameter und der Datentyp jedes Parameters übereinstimmen.

super-Anweisung

Häufig möchten Programmierer beim Überschreiben einer Methode das Verhalten der überschriebenen Methode der übergeordneten Klasse nur ändern und nicht vollständig ersetzen. Dies erfordert einen Mechanismus, mit dem es einer Methode in einer Unterklasse möglich ist, die Version gleichen Namens in der übergeordneten Klasse aufzurufen. Einen solchen Mechanismus bietet die Anweisung super, da sie einen Verweis auf die unmittelbar übergeordnete Klasse enthält. Im folgenden Beispielcode wird eine Klasse namens „Base“ definiert, die eine Methode mit der Bezeichnung thanks() sowie eine Unterklasse der Base-Klasse namens „Extender“ enthält, welche die thanks()-Methode überschreibt. Die Extender.thanks()-Methode verwendet die super-Anweisung zum Aufrufen von 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"; 
    } 
}

Überschreiben von get- und set-Methoden

Im Gegensatz zu Variablen, die in einer übergeordneten Klasse definiert wurden, können get- und set-Methoden überschrieben werden. Im folgenden Beispielcode wird eine get-Methode namens currentLabel überschrieben, die in der MovieClip-Klasse von ActionScript 3.0 definiert ist.:

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

Die Ausgabe der trace()-Anweisung im OverrideExample-Klassenkonstruktor lautet Override: null; dies zeigt, dass dieses Beispiel in der Lage ist, die geerbte Eigenschaft currentLabel zu überschreiben.

Nicht geerbte statische Eigenschaften

Statische Eigenschaften werden von Unterklassen nicht geerbt. Dies bedeutet, dass über die Instanz einer Unterklasse nicht auf statische Eigenschaften zugegriffen werden kann. Der Zugriff auf eine statische Eigenschaft ist nur über das Klassenobjekt möglich, in dem sie definiert wurde. Im folgenden Beispielcode werden eine Basisklasse namens „Base“ sowie eine Unterklasse mit der Bezeichnung „Extender“ definiert, welche die Base-Klasse erweitert. In der Base-Klasse ist eine statische Variable namens test definiert. Der Code im folgenden Auszug kann nicht im strikten Modus kompiliert werden und erzeugt im Standardmodus einen Laufzeitfehler.

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

Die einzige Möglichkeit, auf die statische Variable test zuzugreifen, ist über das Klassenobjekt. Dies wird im folgenden Code gezeigt:

Base.test;

Es ist jedoch auch zulässig, eine Instanzeigenschaft mit dem gleichen Namen wie eine statische Eigenschaft zu definieren. Eine solche Instanzeigenschaft kann in der gleichen Klasse wie die statische Eigenschaft oder in einer Unterklasse definiert werden. So kann die Base-Klasse aus dem vorangegangenen Beispiel eine Instanzeigenschaft namens test aufweisen. Der folgende Beispielcode wird kompiliert und ausgeführt, da die Instanzeigenschaft von der Extender-Klasse geerbt wird. Der Code ließe sich auch dann kompilieren und ausführen, wenn die Definition der Instanzvariablen „test“ in die Extender-Klasse verschoben wird, jedoch nicht, wenn sie dorthin kopiert wird.

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

Statische Eigenschaften und die Gültigkeitsbereichskette

Obwohl statische Eigenschaften nicht geerbt werden, befinden sie sich in der Gültigkeitsbereichskette der Klasse, in der sie definiert wurden, sowie in allen Unterklassen dieser Klasse. Anders ausgedrückt, statische Eigenschaften befinden sich im Gültigkeitsbereich der Klasse, in der sie definiert wurden, sowie in allen Unterklassen. Dies bedeutet, dass auf eine statische Eigenschaft direkt aus dem Klassenrumpf zugegriffen werden kann, in dem sie definiert wird, sowie aus allen Unterklassen dieser Klasse.

Im folgenden Beispielcode werden die im vorangegangenen Beispiel definierten Klassen modifiziert, um zu zeigen, dass sich die in der Base-Klasse definierte statische Variable test auch im Gültigkeitsbereich der Extender-Klasse befindet. Anders ausgedrückt, die Extender-Klasse kann auf die Variable test zugreifen, ohne ihr den Namen der Klasse voranzustellen, in der test definiert ist.

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

Wenn eine Instanzeigenschaft mit dem gleichen Namen wie eine statische Eigenschaft in der gleichen oder einer übergeordneten Klasse definiert ist, so hat die Instanzeigenschaft eine höhere Rangstufe in der Gültigkeitsbereichskette. Man kann sagen, die Instanzeigenschaft verbirgt die statische Eigenschaft, daher wird der Wert der Instanzeigenschaft anstelle des Werts der statischen Eigenschaft verwendet. Im folgenden Code ist dargestellt, dass bei einer in der Extender-Klasse definierten Instanzvariablen namens test die trace()-Anweisung anstelle des Wertes der statischen Variablen den Wert der Instanzvariablen verwendet:

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