Héritage

L’héritage est une forme de réutilisation du code qui permet aux programmeurs de développer de nouvelles classes à partir de classes existantes. Les classes existantes sont alors fréquemment appelées classes de base ou superclasses, alors que les nouvelles classes sont généralement appelées sous-classes. L’un des principaux avantages de l’héritage est qu’il permet de réutiliser le code d’une classe de base sans modifier le code existant. De plus, l’héritage ne nécessite pas de modifier les modes d’interaction des autres classes avec la classe de base. Plutôt que de modifier une classe existante, qui est peut-être soigneusement testée et déjà utilisée, l’héritage permet de traiter cette classe comme un module intégré qui peut être étendu à l’aide de propriétés et de méthodes supplémentaires. C’est pourquoi on utilise le mot-clé extends pour indiquer qu’une classe hérite d’une autre classe.

L’héritage permet également de tirer parti du polymorphisme du code. Le polymorphisme est la possibilité d’utiliser un nom de méthode unique pour une méthode qui se comporte différemment en fonction des types de données qu’elle reçoit. Par exemple, supposons une classe de base appelée Shape, avec deux sous-classes appelées Circle et Square. La classe Shape définit une méthode appelée area() qui renvoie la surface de Shape. Si vous avez implémenté le polymorphisme, vous pouvez appeler la méthode area() pour les objets de type Circle ou Square et lui faire exécuter le calcul correct. L’héritage autorise le polymorphisme en permettant aux sous-classes d’hériter et de définir, ou override, les méthodes de la classe de base. Dans l’exemple suivant, la méthode area() est redéfinie par les classes Circle et 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

Dans la mesure où chaque classe définit un type de données, l’utilisation de l’héritage crée un rapport spécial entre une classe de base et une classe qui l’étend. Une sous-classe possède obligatoirement toutes les propriétés de sa classe de base, ce qui signifie qu’il est toujours possible de substituer une occurrence d’une sous-classe à une occurrence de la classe de base. Par exemple, si une méthode définit un paramètre du type Shape, il est parfaitement valable de lui transmettre un argument du type Circle, car Circle étend Shape, comme on peut le voir ci-dessous :

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

Propriétés et héritage des occurrences

Qu’elle soit définie à l’aide du mot-clé function, var ou const, une propriété d’occurrence est héritée par toutes les sous-classes tant que cette propriété n’est pas déclarée avec l’attribut private dans la classe de base. Par exemple, la classe Event d’ActionScript 3.0 possède des sous-classes nombreuses qui héritent de propriétés communes à tous les objets événements.

Pour certains types d’événements, la classe Event contient toutes les propriétés nécessaires pour définir l’événement. Ces types d’événements ne nécessitent pas de propriétés d’occurrence au-delà de celles qui sont définies dans la classe Event. L’événement complete, qui est déclenché lorsque des données ont été chargées avec succès, et l’événement connect, qui se produit lorsqu’une connexion réseau a été établie, sont des exemples de ce type d’événement.

L’exemple suivant est extrait de la classe Event. Il montre certaines propriétés et méthodes dont les sous-classes héritent. Comme elles sont héritées, ces propriétés sont accessibles par une occurrence de n’importe quelle sous-classe.

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

D’autres types d’événements nécessitent des propriétés uniques qui ne sont pas disponibles dans la classe Event. Ces événements sont définis à l’aide de sous-classes de la classe Event, ce qui permet d’ajouter de nouvelles propriétés à celles de cette classe Event. La classe MouseEvent est un exemple de sous-classe de ce type. Elle ajoute des propriétés uniques aux événements associés à un mouvement ou à un clic de souris, tels que les événements mouseMove et click. L’exemple suivant est extrait de la classe MouseEvent. Il montre la définition des propriétés inhérentes à la sous-classe parce qu’absentes de la classe de base.

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

Spécificateurs de contrôle d’accès et héritage

Si une propriété est déclarée avec le mot-clé public, elle est visible de n’importe quel point du code. Cela signifie que le mot-clé public n’introduit aucune restriction sur l’héritage des propriétés, contrairement aux mots-clés private, protected et internal.

Si une propriété est déclarée avec le mot-clé private, elle n’est visible qu’à partir de la classe qui la définit. Autrement dit les sous-classes n’en héritent pas. Ce comportement est différent de celui des versions antérieures d’ActionScript où le mot-clé private se comportait plutôt comme le mot-clé protected d’ActionScript 3.0.

Le mot-clé protected indique qu’une propriété est visible non seulement à partir de la classe qui la définit, mais aussi à partir de toutes les sous-classes de celle-ci. Contrairement au mot-clé protected en Java, en ActionScript 3.0 le mot-cléprotected ne rend pas une propriété visible à partir de toutes les autres classes du même package. En ActionScript 3.0, seules les sous-classes peuvent accéder à une propriété déclarée avec le mot-clé protected. De plus, une propriété protégée est visible à partir d’une sous-classe même si celle-ci ne se trouve pas dans le même package que sa classe de base.

Pour limiter la visibilité d’une propriété au package dans lequel elle est définie, vous pouvez soit utiliser le mot-clé internal, soit n’utiliser aucun spécificateur de contrôle d’accès. Le spécificateur de contrôle d’accès internal est appliqué par défaut si vous n’en indiquez aucun. Seule une sous-classe résidant dans le même package pourra hériter d’une propriété marquée comme internal. Seule une sous-classe résidant dans le même package peut hériter d’une propriété désignée comme internal.

L’exemple suivant montre comment chaque spécificateur de contrôle d’accès affecte l’héritage dans et au-delà des package. Le code ci-dessous définit une classe principale d’application appelée AccessControl et deux autres classes, Base et Extender. La classe Base se trouve dans un package appelé foo et la classe Extender, qui est une sous-classe de la classe Base, dans un package appelé bar. La classe AccessControl n’importe que la classe Extender et crée une occurrence de celle-ci qui tente d’accéder à une variable appelée str, définie dans la classe Base. La variable str est déclarée comme public, si bien que le code est compilé et exécuté comme l’illustre l’exemple ci-dessous :

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

Pour voir l’effet des autres spécificateurs de contrôle d’accès lors de la compilation et de l’exécution de cet exemple, changez le spécificateur de contrôle de la variable str à private, protected ou internal après avoir supprimé ou mis en commentaire la ligne suivante dans la classe AccessControl :

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

Redéfinition des variables impossible

Les propriétés déclarées à l’aide des mots-clés var ou const sont héritées mais ne peuvent pas être redéfinies. Redéfinir, ou forcer, une propriété au sens de « override » signifie redéfinir cette propriété dans une sous-classe. Les accesseurs get et set (c’est-à-dire les propriétés déclarées avec le mot-clé function) constituent le seul type de propriété qu’il est possible de redéfinir. Bien qu’il soit impossible de redéfinir une variable d’occurrence, vous pouvez obtenir le même résultat par la création des méthodes de lecture et de définition pour cette variable d’occurrence et en forçant ces méthodes.

Redéfinition des méthodes

Redéfinir une méthode signifie redéfinir le comportement d’une méthode héritée. Les méthodes statiques ne sont pas héritées et ne peuvent donc pas être redéfinies. Toutefois, les sous-classes héritent des méthodes d’occurrence et il est donc possible de redéfinir celles-ci sous réserve de deux conditions :

  • La méthode d’occurrence ne doit pas être déclarée avec le mot-clé final dans la classe de base. Lorsqu’il est utilisé avec une méthode d’occurrence, le mot-clé final indique que le programmeur veut empêcher les sous-classes de redéfinir cette méthode.

  • La méthode d’occurrence ne doit pas être déclarée avec le contrôle d’accès private dans la classe de base. Si une méthode est désignée comme private dans la classe de base, il est inutile d’utiliser le mot-clé override lors de la définition d’une méthode portant le même nom dans la sous-classe, puisque la méthode de la classe de base n’est pas visible à partir de la sous-classe.

    Pour redéfinir une méthode d’occurrence correspondant à ces critères, la définition de la méthode dans la sous-classe doit utiliser le mot-clé override et correspondre, comme défini ci-dessous, à la version de cette méthode dans la superclasse :

  • La méthode de redéfinition doit avoir le même niveau de contrôle d’accès que celle de la classe de base. Les méthodes définies comme internes doivent avoir le même niveau de contrôle d’accès que celles qui n’ont pas de spécificateur de contrôle d’accès.

  • La méthode de redéfinition doit avoir le même nombre de paramètres que celle de la classe de base.

  • Les paramètres de la méthode de redéfinition doivent avoir les mêmes annotations de type de données que ceux de la méthode de la classe de base.

  • La méthode de redéfinition doit avoir le même type de renvoi que celle de la classe de base.

Toutefois, il n’est pas nécessaire que les noms des paramètres de la méthode de redéfinition correspondent à ceux des paramètres de la classe de base, tant qu’il y a une correspondance entre le nombre de paramètres et leurs types de données.

Instruction super

Il arrive fréquemment que les programmeurs souhaitent compléter le comportement de la méthode de superclasse qu’ils redéfinissent, plutôt que de remplacer ce comportement. Il est donc nécessaire de disposer d’un mécanisme permettant à une méthode d’une sous-classe d’appeler sa propre superclasse. Ce mécanisme est assuré par l’instruction super qui contient une référence à la superclasse immédiate. L’exemple suivant définit une classe appelée Base, qui contient la méthode appelée thanks() et une sous-classe de Base appelée Extender, qui redéfinit la méthode thanks(). La méthode Extender.thanks() utilise l’instruction super pour appeler 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"; 
    } 
}

Redéfinition des accesseurs Get et Set

Bien qu’il soit impossible de redéfinir les variables définies dans une superclasse, il est possible de redéfinir les accesseurs Get et Set. Par exemple, le code suivant redéfinit un accesseur Get appelé currentLabel qui est défini dans la classe MovieClip d’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; 
        } 
    } 
}

Le résultat de l’instruction trace() dans le constructeur de la classe OverrideExample est Override: null, ce qui montre que cet exemple a réussi à redéfinir la propriété héritéecurrentLabel.

Propriétés statiques non héritées

Les sous-classes n’héritent pas des propriétés statiques. Ces dernières ne sont donc pas accessibles par une occurrence d’une sous-classe. Une propriété statique n’est accessible que par le biais de l’objet classe dans lequel elle est définie. Par exemple, le code suivant définit une classe de base appelée Base et une sous-classe appelée Extender, qui étend la classe Base. Une variable statique appelée test est définie dans la classe Base. Le code de l’extrait ci-dessous ne peut être compilé en mode strict et génère une erreur d’exécution en mode standard.

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

La seule façon d’accéder à la variable statique test est par l’objet classe, comme le montre le code suivant :

Base.test;

Il est toutefois permis de définir une propriété d’occurrence ayant le même nom qu’une propriété statique. Cette propriété d’occurrence peut être définie dans la même classe que la propriété statique, ou dans une sous-classe. Par exemple, la classe Base de l’exemple précédent peut comporter une propriété d’occurrence appelée test. Le code suivant peut être compilé et exécuté normalement, car la classe Extender hérite de la propriété d’occurrence. Ce code peut aussi être compilé et exécuté normalement si la définition de la variable d’occurrence test est déplacée (mais non copiée) dans la classe 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 {}

Propriétés statiques et chaîne de portée

Bien que les propriétés statiques ne soient pas héritées, elles figurent dans la chaîne de portée de la classe qui les définit ainsi que de toute sous-classe de celle-ci. C’est pourquoi on dit que les propriétés statiques sont dans la portée à la fois de la classe dans laquelle elles sont définies et des sous-classes de celle-ci. Cela signifie qu’une propriété statique est directement accessible à partir du corps de la classe qui la définit et de toute sous-classe de celle-ci.

L’exemple suivant modifie les classes définies dans l’exemple précédent pour montrer que la variable statique test, définie dans la classe Base, est dans la portée de la classe Extender. Autrement dit, la classe Extender peut accéder à la variable statique test sans qu’il ne soit nécessaire de faire précéder le nom de celle-ci du nom de la classe qui définit 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 
    } 
     
}

Si une propriété d’occurrence est définie avec le même nom qu’une propriété statique de la même classe ou d’une superclasse, la propriété d’occurrence est prioritaire dans la chaîne de portée. On dit alors que la propriété d’occurrence fait de l’ombre à la propriété statique, ce qui signifie que la valeur de la propriété d’occurrence est utilisée à la place de celle de la propriété statique. Par exemple, le code ci-dessous montre que si la classe Extender définit une variable d’occurrence appelée test, l’instruction trace() utilise la valeur de la variable d’occurrence à la place de celle de la variable statique.

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