Overerving

Overerving is een soort hergebruik van code waardoor programmeurs in staat zijn nieuwe klassen te ontwikkelen die op bestaande klassen zijn gebaseerd. De bestaande klassen worden vaak basisklassen of superklassen genoemd, terwijl de nieuwe klassen doorgaans subklassen worden genoemd. Het grootste voordeel van overerving is dat u de code van basisklassen opnieuw kunt gebruiken en de bestaande code ongewijzigd laat. Bovendien hoeft de manier waarop andere klassen met de basisklasse communiceren niet te worden gewijzigd. In plaats van een bestaande klasse te wijzigen die waarschijnlijk al grondig is getest of al in gebruik is, kunt u met overerving een klasse als een geïntegreerde module behandelen die u met aanvullende eigenschappen of methoden kunt uitbreiden. U kunt het trefwoord extends dus gebruiken om aan te geven dat een klasse overerft van een andere klasse.

Dankzij overerving kunt u gebruikmaken van polymorfisme in de code. Met polymorfisme kunt u een enkele methodenaam gebruiken voor een methode die zich anders gedraagt, afhankelijk van het gegevenstype waarop deze wordt toegepast. Een eenvoudig voorbeeld is een basisklasse met de naam Shape, die twee subklassen met de naam Circle en Square bevat. De klasse Shape definieert een methode met de naam area(), die het gebied van de vorm retourneert. Als polymorfisme wordt geïmplementeerd, kunt u de methode area() aanroepen voor objecten van het type Circle en Square en deze de correcte berekeningen voor u laten uitvoeren. Overerving staat polymorfisme toe door subklassen toe te staan methoden van de basisklasse te overerven, opnieuw te definiëren of te overschrijven. In het volgende voorbeeld wordt de methode area() opnieuw gedefinieerd door de klassen Circle en 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

Aangezien elke klasse een gegevenstype definieert, brengt het gebruik van overerving een speciale relatie tot stand tussen een basisklasse en de klasse die deze uitbreidt. Een subklasse heeft altijd beschikking tot de eigenschappen van de basisklasse; dit houdt in dat een instantie van een subklasse altijd kan worden vervangen door een instantie van een basisklasse. Als een methode bijvoorbeeld een parameter van het type Shape definieert, is het mogelijk een argument van het type Circle door te geven, omdat Circle de Shape uitbreidt. Dit wordt getoond met de volgende code:

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

Instantie-eigenschappen en overerving

Een instantie-eigenschap, of die nu met het trefwoord function, var of const is gedefinieerd, wordt overgeërfd door alle subklassen, op voorwaarde dat de eigenschap in de basisklasse niet met het attribuut private is gedefinieerd. De klasse Event in ActionScript 3.0 bevat bijvoorbeeld een aantal subklassen die eigenschappen overerven die van toepassing zijn op alle gebeurtenisobjecten.

Voor sommige gebeurtenissen bevat de klasse Event alle benodigde eigenschappen om de gebeurtenis te definiëren. Deze gebeurtenissen vereisen naast de gedefinieerde instantie-eigenschappen in de klasse Event geen aanvullende instantie-eigenschappen. Voorbeelden van dergelijke gebeurtenissen zijn de gebeurtenis complete, die optreedt wanneer gegevens zijn geladen, en de gebeurtenis connect, die optreedt wanneer een netwerkverbinding tot stand is gebracht.

Het volgende voorbeeld is een fragment uit de klasse Event die enkele van de eigenschappen en methoden toont die door subklassen worden overgeërfd. Omdat de eigenschappen worden overgeërfd, heeft een instantie van een subklasse toegang tot deze eigenschappen.

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

Overige typen gebeurtenissen vereisen unieke eigenschappen die niet beschikbaar zijn in klasse Event. Deze gebeurtenissen worden gedefinieerd met gebruik van de subklassen van de klasse Event zodat nieuwe eigenschappen aan de eigenschappen kunnen worden toegevoegd die in de klasse Event zijn gedefinieerd. Een voorbeeld van een dergelijke subklasse is de klasse MouseEvent; deze klasse voegt eigenschappen toe die uniek zijn voor gebeurtenissen die zijn gekoppeld aan muisbewegingen of muisklikken, zoals de gebeurtenissen mouseMove en click. Het volgende voorbeeld is een fragment van de klasse MouseEvent die de definitie van eigenschappen toont die bestaat in de subklasse, maar niet in de basisklasse:

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

Toegangsbeheerspecificaties en overerving

Als een eigenschap is gedeclareerd met het trefwoord public, is de eigenschap zichtbaar voor alle code. Dit houdt in dat het trefwoord public, in tegenstelling tot de trefwoorden private, protected en internal, geen beperkingen oplevert voor het overerven van eigenschappen.

Als een eigenschap is gedeclareerd met het trefwoord private, is deze alleen zichtbaar in de klasse die de eigenschap definieert; de eigenschap kan dus niet door subklassen worden overgeërfd. Dit gedrag verschilt met vorige versies van ActionScript, waar het trefwoord private zich meer als het trefwoord protected van ActionScript 3.0 gedroeg.

Het trefwoord protected geeft aan dat een eigenschap niet alleen zichtbaar is binnen de klasse die de eigenschap heeft gedefinieerd, maar ook voor alle subklassen. In tegenstelling tot het trefwoord protected in de Java-programmeertaal, maakt het trefwoord protected in ActionScript 3.0 een eigenschap niet zichtbaar voor alle andere klassen in hetzelfde pakket. In ActionScript 3.0 hebben alleen subklassen toegang tot een eigenschap die is gedeclareerd met het trefwoord protected. Bovendien is een beschermde eigenschap zichtbaar voor een subklasse, ongeacht of de subklasse zich in hetzelfde pakket als de basisklasse bevindt of in een ander pakket.

Als u de zichtbaarheid van een eigenschap wilt beperken voor het pakket waarin deze is gedefinieerd, gebruikt u het trefwoord internal of maakt u geen gebruik van een toegangsbeheerspecificatie. De toegangsbeheerspecificatie internal is de toegangsbeheerspecificatie die standaard wordt gebruikt wanneer er geen is opgegeven. Een eigenschap die is gemarkeerd als internal, wordt alleen door een subklasse overgeërfd die zich in hetzelfde pakket bevindt.

U kunt aan de hand van het volgende voorbeeld zien hoe elk van de toegangsbeheerspecificaties pakketgrensoverschrijdende overerving beïnvloedt. De volgende code definieert een hoofdtoepassingsklasse met de naam AccessControl en twee andere klassen met de naam Base en Extender. De klasse Base bevindt zich in een pakket met de naam foo en de klasse Extender, een subklasse van de klasse Base, bevindt zich in een pakket met de naam bar. De klasse AccessControl importeert alleen de klasse Extender en maakt een instantie van Extender die toegang probeert te krijgen tot een variabele met de naam str, die in de klasse Base is gedefinieerd. De variabele str is gedeclareerd als public, zodat de code wordt gecompileerd en uitgevoerd zoals getoond in het volgende fragment:

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

Als u het effect van de overige toegangsbeheerspecificaties wilt zien op de compilatie en uitvoering van het vorige voorbeeld, wijzigt u de toegangsbeheerspecificatie van de variabele str in private, protected of internal nadat u de volgende regel uit de klasse AccessControl hebt verwijderd of genegeerd:

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

Variabelen overschrijven niet toegestaan

Eigenschappen die zijn gedeclareerd met de trefwoorden var of const, worden overgeërfd, maar kunnen niet worden overschreven. Als u een eigenschap wilt overschrijven, moet de eigenschap in een subklasse opnieuw worden gedefinieerd. De enige type eigenschap die u kunt overschrijven, zijn get/set-methoden, oftewel eigenschappen die zijn gedeclareerd met het trefwoord function). Hoewel u een instantievariabele niet kunt overschrijven, kunt u een vergelijkbare functionaliteit verkrijgen door methoden getter en setter te maken voor de instantievariabele en de methoden te overschrijven.

Methoden overschrijven

Als u een methode wilt overschrijven, moet u het gedrag van een overgeërfde methode opnieuw definiëren. Statische methoden worden niet overgeërfd en kunnen niet worden overschreven. Instantiemethoden worden echter door subklassen overgeërfd en kunnen worden overschreven zolang er aan de volgende twee criteria wordt voldaan:

  • De instantiemethode wordt niet gedeclareerd met het trefwoord final in de basisklasse. Bij gebruik met een instantiemethode geeft het trefwoord final de poging van de programmeur aan te verhinderen dat subklassen de methode overschrijven.

  • De instantiemethode wordt niet gedeclareerd met de toegangsbeheerspecificatie private in de basisklasse. Als een methode in de basisklasse als private is gemarkeerd, is het niet nodig het trefwoord override te gebruiken wanneer u een methode met een identieke naam in de subklasse definieert, omdat de basisklassenmethode niet zichtbaar is voor de subklasse.

    Als u een instantiemethode wilt overschrijven die aan deze criteria voldoet, moet de methodedefinitie in de subklasse gebruik maken van het trefwoord override en moet als volgt overeenkomen met de superklassenversie van de methode:

  • De overschrijfmethode moet hetzelfde toegangsbeheerniveau hebben als de basisklassenmethode. Methoden die als internal zijn gemarkeerd, hebben hetzelfde toegangsbeheerniveau als methoden die geen toegangsbeheerspecificatie hebben.

  • De overschrijfmethode moet hetzelfde aantal parameters hebben als de basisklassenmethode.

  • De overschrijfmethodeparameters moeten dezelfde gegevenstypeannotaties hebben als de parameters in de basisklassenmethode.

  • De overschrijfmethode moet hetzelfde retourneringstype hebben als de basisklassenmethode.

De namen van de parameters in de overschrijfmethoden hoeven echter niet overeen te komen met de namen van de parameters in de basisklasse, zolang als het aantal parameters en het gegevenstype van elke parameter overeenkomt.

Instructie super

Wanneer een programmeur een methode overschrijft, wil deze vaak het gedrag van de superklassenmethode uitbreiden in plaats van het gedrag volledig te vervangen. Dit vereist een mechanisme dat een methode in een subklasse toestaat de superklassenversie van zichzelf aan te roepen. De instructie super biedt een dergelijk mechanisme, deze bevat namelijk een verwijzing naar de directe superklasse. Het volgende voorbeeld definieert een klasse met de naam Base die een methode bevat met de naam thanks() en een subklasse van de klasse Base met de naam Extender, die de methode thanks() overschrijft. De methode Extender.thanks() gebruikt de instructie super om Base.thanks() aan te roepen.

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

Getters en setters overschrijven

Hoewel u geen variabelen kunt overschrijven die in een superklasse zijn gedefinieerd, kunt u getters en setters overschrijven. De volgende code heeft bijvoorbeeld de getter currentLabel op die in de klasse MovieClip van ActionScript 3.0 is gedefinieerd:

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

De uitvoer van de instructie trace() in de klassenconstructor OverrideExample is Override: null, waaruit blijkt dat in het voorbeeld de overgeërfde eigenschap currentLabel kon worden overschreven.

Statische niet-overgeërfde eigenschappen

Statische eigenschappen worden niet door subklassen overgeërfd. Dit houdt in dat statische eigenschappen niet kunnen worden benaderd via de instantie van een subklasse. Een statische eigenschap kan alleen worden benaderd via het klassenobject waarvoor die is gedefinieerd. De volgende code definieert bijvoorbeeld een basisklasse met de naam Base en een subklasse die Base uitbreidt en de naam Extender draagt. Een statische variabele met de naam test wordt in de klasse Base gedefinieerd. De code zoals geschreven in het volgende fragment compileert niet in strikte modus en genereert een uitvoerfout in de standaardmodus.

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

De enige manier waarop de statische variabele test kan worden benaderd, is via het klassenobject, zoals getoond in de volgende code:

Base.test;

Het is echter toegestaan om een instantie-eigenschap te definiëren met dezelfde naam als een statische eigenschap. Een dergelijke instantie-eigenschap kan worden gedefinieerd in dezelfde klasse als de statische eigenschap of in een subklasse. De klasse Base in het vorige voorbeeld kan bijvoorbeeld een instantie-eigenschap met de naam test hebben. De volgende code wordt gecompileerd en uitgevoerd, omdat de instantie-eigenschap is overgeërfd door de klasse Extender. De code zou ook worden gecompileerd en uitgevoerd als de definitie van de testinstantievariabele wordt verplaatst, maar niet gekopieerd, naar de klasse 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 {}

Statische eigenschappen en de bereikketen

Hoewel statische eigenschappen niet worden overgeërfd, bevinden ze zich in de bereikketen van de klasse die ze definieert en alle subklassen van die klasse. Hierdoor wordt gezegd dat statische eigenschappen in het bereik liggen van de klasse waarin ze zijn gedefinieerd en alle subklassen. Dit houdt in dat een statische eigenschap direct toegankelijk is binnen de hoofdtekst van de klasse die de statische eigenschap definieert en alle subklassen van die klasse.

Het volgende voorbeeld wijzigt de klassen die in het vorige voorbeeld zijn gedefinieerd, om aan te tonen dat de statische variabele test die is gedefinieerd in de klasse Base, in het bereik ligt van de klasse Extender. Met andere woorden, de klasse Extender kan de statische variabele test benaderen zonder de naam van de klasse die test definieert als voorvoegsel aan de variabele toe te voegen.

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

Als een instantie-eigenschap is gedefinieerd die dezelfde naam gebruikt als een statische eigenschap in dezelfde klasse of een superklasse, heeft de instantie-eigenschap een hogere prioriteit in de bereikketen. De instantie-eigenschap schaduwt de statische eigenschap, wat betekent dat de waarde van de instantie-eigenschap wordt gebruikt in plaats van de waarde van de statische eigenschap. De volgende code toont bijvoorbeeld dat als de klasse Extender een instantievariabele met de naam test definieert, de instructie trace() gebruikmaakt van de waarde van de instantievariabele in plaats van de waarde van de statische variabele.

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