• 12 heures
  • {0} Facile|{1} Moyenne|{2} Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Mis à jour le 30/04/2014

L'héritage

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce chapitre, nous allons parler d'héritage !
Sachez qu'il s'agit de l'une des notions les plus importantes de la Programmation Orientée Objet et qui en font tout son intérêt !
Nous détaillerons ce concept tout au long du chapitre, mais pour vous situer rapidement ce dont il s'agit, cela permet de créer une ou des nouvelles classes en se basant sur une autre. Ainsi, nous pouvons écrire des classes qui sont similaires en utilisant une autre classe qui regroupe l'ensemble des propriétés communes.
Dans ce chapitre, la seule difficulté que vous aurez à affronter sera la notion d'héritage en elle-même. Au niveau du code, il n'y aura pas énormément de nouveautés, ce qui vous simplifiera la vie !

La notion d'héritage

Nous avons déjà brièvement présenté le concept dans les parties précédentes, cependant, vu son importance, il n'est pas superflu d'y revenir !
L'héritage permet de créer une ou des nouvelles classes en réutilisant le code d'une classe déjà existante. On parle alors de classe de base ou superclasse ou encore classe-mère pour cette dernière, et de sous-classes ou classes-filles pour les classes héritées de celle-ci. L'idée est donc ici d'étendre une classe de base, notamment en lui ajoutant de nouvelles propriétés.
D'ailleurs, le mot-clé qui permet d'étendre une classe est extends, nous l'avons déjà croisé dans le deuxième chapitre :

public class Hello extends Sprite {
    
}

Nous reviendrons sur la syntaxe Actionscript plus tard. Pour l'instant, nous allons principalement nous focaliser sur la notion d'héritage.

Quand est-ce utile d'utiliser l'héritage ?

C'est en effet une question à laquelle les débutants ont souvent du mal à répondre. En réalité, nous pouvons introduire une relation d'héritage lorsque la condition suivante est respectée :
la sous-classe est un sous-ensemble de la superclasse.

Ce terme mathématique barbare signifie que la sous-classe appartient à l'ensemble de la superclasse. Si ce n'est pas encore bien clair, voici quelques exemples qui vous aideront à bien comprendre :

  • L'Actionscript appartient à l'ensemble des langages de programmation.

  • Les fourmis appartiennent à l'ensemble des insectes.

  • Les avions appartiennent à l'ensemble des véhicules.

  • Les voitures appartiennent également à l'ensemble des véhicules.

  • Les 4L appartiennent à l'ensemble des voitures.

Comme vous pouvez le constater, les relations précédentes ne peuvent s'effectuer que dans un seul sens. Vous remarquerez également, d'après les deux derniers exemples, qu'il est possible d'avoir plusieurs niveaux d'héritage.

En quoi est-ce différent d'une instance de classe ?

Il est vrai qu'il serait possible, par exemple, de créer des instances Avion et Voiture d'une classe Vehicule. Nous pourrions de cette manière définir des valeurs distinctes pour chacun des attributs afin de les différencier.
En utilisant le concept d'héritage, nous pourrions écrire deux sous-classes Avion et Voiture qui hériteraient de l'ensemble des attributs et méthodes de la superclasse, et ce sans réécrire le code à l'intérieur de celles-ci. Mais tout l'intérêt vient du fait que l'utilisation de l'héritage nous permet de définir de nouveaux attributs et de nouvelles méthodes pour nos sous-classes.
Sachez qu'il est également possible de redéfinir des méthodes de la superclasse, mais nous en reparlerons quand nous introduirons le polymorphisme plus loin dans ce chapitre !

Construction d'un héritage

Dans la suite de ce chapitre, nous allons principalement nous intéresser aux manipulations du côté des classes-filles. Cependant, nous aurons besoin d'une superclasse à partir de laquelle nous pourrons travailler. Ainsi, je vous propose une classe Vehicule, dont le code est donné ci-dessous :

package
{
    public class Vehicule
    {
        protected var _marque:String;
        protected var _vitesse:int;
        
        public function Vehicule(marque:String, vitesse:int)
        {
            _marque = marque;
            _vitesse = vitesse;
        }
        
        public function get marque():String
        {
            return _marque;
        }
        
        public function get vitesse():int
        {
            return _vitesse;
        }
        
        public function set vitesse(vitesse:int):void
        {
            _vitesse = vitesse;
        }

    }
    
}

La portée protected

Nous avions rapidement mentionné cette portée sans en expliquer vraiment le fonctionnement. Maintenant vous savez comment fonctionne l'héritage, nous allons pouvoir utiliser cette nouvelle portée qu'est protected !
Cette portée a été introduite afin de rendre les propriétés visibles non seulement depuis la classe où elles sont définies comme private, mais également depuis l'ensemble de ses sous-classes.
Voyez les attributs de la classe Vehicule définie juste avant :

protected var _marque:String;
protected var _vitesse:int;

Ces attributs seront donc visibles depuis les éventuelles sous-classes que nous pourrons définir.

À présent, vous connaissez l'ensemble des portées que propose l'Actionscript, dont voici un petit récapitulatif :

  • public : propriété visible n'importe où.

  • private : propriété visible uniquement à l'intérieur de la classe qui l'a définie.

  • protected : propriété visible depuis la classe où elle est définie ainsi que depuis l'ensemble de ses sous-classes.

  • internal : propriété visible depuis l'ensemble du package où elle est définie.

Construction des sous-classes

L'héritage

Comme nous l'avons dit en début de chapitre, l'héritage se fait en « étendant » une classe à l'aide du mot-clé extends comme ceci :

package
{
    public class Voiture extends Vehicule
    {
        
    }
}

Par cette simple extension, la classe Voiture hérite donc de l'ensemble des propriétés de la classe Vehicule. Il est toutefois nécessaire de lui rajouter au moins un constructeur, et, éventuellement, quelques propriétés supplémentaires pour lui donner de l'intérêt.
Par exemple, nous pourrions introduire un attribut _traction pour définir si la voiture est une traction ou non, ou encore un attribut _immatriculation pour identifier celle-ci :

package
{
    public class Voiture extends Vehicule
    {
        protected var _traction:Boolean;
        protected var _immatriculation:String;
    }
}
Le constructeur

Comme toute classe, notre sous-classe Voiture doit posséder un constructeur. Cependant, celle-ci hérite d'une classe-mère qui possède son propre constructeur qui est nécessaire à l'initialisation des attributs. Depuis une classe-fille, il est possible d'appeler le constructeur de sa superclasse par la fonction super() :

public function Voiture()
{
    super();
    // Instructions supplémentaires
}

Voici donc un exemple de constructeur pour notre classe Voiture :

public function Voiture(marque:String, vitesse:int, immatriculation:String)
{
    super(marque, vitesse);
    _immatriculation = immatriculation;
    _traction = true;
}
Les méthodes

En plus de pouvoir définir de nouveaux attributs, il est possible de rajouter autant de méthodes que nous souhaitons à l'intérieur d'une sous-classe. Étant donné nous utilisons encore le concept d'encapsulation, nous commencerons par créer des accesseurs à ce nouvel attribut. Je vous propose de découvrir quelques-uns d'entre eux :

public function set immatriculation(nouvelleImmatriculation:String):void
{
    _immatriculation = nouvelleImmatriculation;
}

public function get immatriculation():String
{
    return _immatriculation;
}

Comme vous pouvez le voir, ces méthodes fonctionnent exactement de la même manière que pour une classe quelconque. En revanche, ce qui peut être intéressant, c'est d'utiliser les méthodes définies à l'intérieur de la classe mère. Ainsi, si les méthodes de votre classe mère ont une portée public ou protected, celles-ci sont accessibles depuis les classes filles. Nous avons ainsi un mot-clé super qui nous permet de faire référence à la superclasse et d'utiliser ses propriétés.
Voici, par exemple, une méthode nommée accelerer() qui permet d'augmenter la vitesse du véhicule :

public function accelerer():void
{
    var nouvelleVitesse:int = super.vitesse + 15;
    super.vitesse = nouvelleVitesse;
}

Néanmoins, le mot-clé super est facultatif dans la plupart des cas. Le code ci-dessous est tout à fait fonctionnel :

public function accelerer():void
{
    var nouvelleVitesse:int = vitesse + 15; // L'accesseur de la classe-mère sera automatiquement sélectionné
    vitesse = nouvelleVitesse;
}

Il peut même être simplifié (vu que vous avez compris le principe) :

public function accelerer():void
{
    vitesse += 15;
}

Comme je n'ai pas tout réécrit, je vous propose tout de même un schéma UML résumant les propriétés des deux classes Vehicule et Voiture ainsi que le lien qui les unit :

Héritage entre les classes Vehicule et Voiture
Héritage entre les classes Vehicule et Voiture

La substitution d'une sous-classe à une superclasse

Un autre avantage de l'utilisation de l'héritage est le fait de pouvoir substituer une sous-classe à une superclasse. C'est-à-dire qu'il est possible de manipuler une classe-fille comme s'il s'agissait d'une instance de la classe-mère.

Parce qu'un exemple vaut mille mots, prenons le code suivant :

var MesVehicules:Array = new Array();
MesVehicules.push(new Vehicule("Airbus A380", 900));
MesVehicules.push(new Vehicule("Bicyclette", 25));
MesVehicules.push(new Voiture("Renault 4L", 100, "911 SDZ 15"));
for (var i:int = 0; i < MesVehicules.length; i++)
{
    trace("Un véhicule de type " + MesVehicules[i].marque + " peut se déplacer à la vitesse de " + MesVehicules[i].vitesse + "km/h.");
}
/* Affiche :
Un véhicule de type Airbus A380 peut se déplacer à la vitesse de 900km/h.
Un véhicule de type Bicyclette peut se déplacer à la vitesse de 25km/h.
Un véhicule de type Renault 4L peut se déplacer à la vitesse de 100km/h.
*/

Il n'y a rien de surprenant dans cet exemple, les accesseurs de la classe Vehicule sont bien accessibles depuis la classe Voiture. En revanche, ce qui deviendrait intéressant, ce serait de créer une méthode presenter() qui permet de présenter un objet de type Vehicule, comme ci-dessous :

var MesVehicules:Array = new Array();
MesVehicules.push(new Vehicule("Airbus A380", 900));
MesVehicules.push(new Vehicule("Bicyclette", 25));
MesVehicules.push(new Voiture("Renault 4L", 100, "911 SDZ 15"));
function presenter(unVehicule:Vehicule):void 
{
    trace("Un véhicule de type " + unVehicule.marque + " peut se déplacer à la vitesse de " + unVehicule.vitesse + "km/h.");
}
for (var i:int = 0; i < MesVehicules.length; i++)
{
    presenter(MesVehicules[i]);
}
/* Affiche :
Un véhicule de type Airbus A380 peut se déplacer à la vitesse de 900km/h.
Un véhicule de type Bicyclette peut se déplacer à la vitesse de 25km/h.
Un véhicule de type Renault 4L peut se déplacer à la vitesse de 100km/h.
*/

Comment se fait-il qu'il n'y ait pas d'erreur pour l'objet de type Voiture ?

Comme nous l'avons dit plus haut dans ce cours, nous pouvons substituer une sous-classe à une superclasse. En d'autres termes, il est possible d'utiliser une classe-fille comme s'il s'agissait de la classe-mère. D'ailleurs, si vous vous rappelez bien nous avions dit qu'une sous-classe était un sous-ensemble de la superclasse, ce qui veut dire qu'une Voitureest un Vehicule. Il n'est donc pas surprenant de pouvoir utiliser un objet de type Voiture en tant que Vehicule.

Le polymorphisme

Nous allons maintenant parler du polymorphisme !
Nous avons là-encore un nom barbare associé à un concept qui n'est pas si compliqué en réalité.

Précédemment, nous avons appris à étendre une superclasse en ajoutant de nouvelles méthodes à l'intérieur d'une sous-classe. Cependant, il est également possible de redéfinir (réécrire) une méthode. Ainsi, nous avons la possibilité d'utiliser un nom de méthode commun pour une méthode qui se comportera différemment suivant le type de l'objet.

Pour vous montrer cela, nous allons insérer une nouvelle méthode qu'on nommera sePresenter() à l'intérieur de la classe Vehicule :

public function sePresenter():void
{
    trace("Un véhicule de type " + marque + " peut se déplacer à la vitesse de " + vitesse + "km/h.");
}

Si nous ne faisons rien, la classe Voiture héritera de cette nouvelle méthode et nous pourrons l'utiliser sans problème. Cependant, nous aimerions personnaliser le message, notamment en rajoutant son numéro d'immatriculation qui permet également de l'identifier.
Nous devrons donc réécrire la méthode sePresenter() pour la classe Voiture. Heureusement, nous disposons d'un mot-clé override, qui permet justement de redéfinir une méthode. Voici comment l'utiliser :

override public function sePresenter():void
{
    trace("Une voiture " + marque + " peut se déplacer à la vitesse de " + vitesse + "km/h.");
    trace("Son immatriculation est : " + immatriculation);
}

Ainsi, nous pouvons utiliser la méthode sePresenter() sans nous soucier du type d'objets que nous sommes en train de manipuler.

var MesVehicules:Array = new Array();
MesVehicules.push(new Vehicule("Airbus A380", 900));
MesVehicules.push(new Vehicule("Bicyclette", 25));
MesVehicules.push(new Voiture("Renault 4L", 100, "911 SDZ 75"));
for (var i:int = 0; i < MesVehicules.length; i++)
{
    MesVehicules[i].sePresenter();
}
/* Affiche :
Un véhicule de type Airbus A380 peut se déplacer à la vitesse de 900km/h.
Un véhicule de type Bicyclette peut se déplacer à la vitesse de 25km/h.
Une voiture Renault 4L peut se déplacer à la vitesse de 100km/h.
Son immatriculation est : 911 SDZ 75
*/

Les attributs de classe

Lorsque nous avions parlé d'encapsulation, nous avions introduit les droits d'accès pour les différentes propriétés d'une classe. Or, pour ceux qui l'auraient également remarqué, nous avons depuis le départ toujours inséré le mot-clé public devant la définition de chacune de nos classes.

Il existe aussi des droits d'accès pour les classes ?

En effet, tout comme les propriétés, les classes possèdent des droits d'accès qui permettent de définir comment nous pouvons accéder à la classe et même la modifier. En réalité, on parle d'attributs de classes et d'attributs de propriétés de classes, cependant, nous emploierons dans ce cours, le terme « droits d'accès » pour éviter la confusion avec les variables internes aux classes appelées également attributs.

Les différents droits d'accès

En ce qui concerne la définition de classes, l'Actionscript propose quatre droits d'accès différents. Sans plus attendre, je vous propose de les découvrir :

  • public : les droits d'accès « publiques » permettent comme pour les propriétés, de rendre la classe visible et accessible partout dans le code. Il s'agit des droits d'accès recommandés dans la majorité des cas.

  • internal : identiquement aux propriétés, les droits d'accès « internes » restreignent l'accessibilité de la classe au package où elle est définie uniquement. Ces droits d'accès ne sont pas très utilisés, mais il s'agit de la valeur par défaut lors d'une définition de classe.

  • final : ces droits d'accès sont directement liés à la notion d'héritage. Le terme « final » fait ici référence au fait que la classe ne peut plus être étendue par une autre classe.

  • dynamic : ce mot-clé permet de définir une classe dynamique, c'est-à-dire modifiable depuis l'extérieur de celle-ci, à l'opposé des classes classiques dites scellées. Nous reviendrons sur ce concept un peu particulier dans le prochain chapitre.

Exemple d'utilisation

Vous l'aurez compris, ces droits d'accès s'utilisent également devant la déclaration de la classe en question. Voici par exemple la définition de la classe Vehicule que nous avons réalisée au début du chapitre :

public class Vehicule
{
    
}

Nous allons essayer ici de comprendre un peu mieux l'utilité du mot-clé final, étroitement lié au concept d'héritage !
Ce mot-clé permet de définir la classe à laquelle il est associé, comme étant la dernière classe qui finalise l'arborescence d'héritage. Cela signifie que cette classe peut très bien hériter d'une autre classe, mais en aucun cas vous ne pourrez créer de classes-filles à celle-ci.
Je vous propose donc une petite manipulation afin de vérifier la véracité de ces propos. Redéfinissez donc la classe Vehicule de type final :

package
{
    final class Vehicule
    {
        protected var _marque:String;
        protected var _vitesse:int;
        
        public function Vehicule(marque:String, vitesse:int)
        {
            _marque = marque;
            _vitesse = vitesse;
        }
        
        public function get marque():String
        {
            return _marque;
        }
        
        public function get vitesse():int
        {
            return _vitesse;
        }
        
        public function set vitesse(vitesse:int):void
        {
            _vitesse = vitesse;
        }

    }
    
}

Nous avons donc maintenant une classe Vehicule qu'il nous est interdit d'étendre. Voyons donc ce qui se passe lorsqu'on tente d'en créer une sous-classe :

package
{
    public class Voiture extends Vehicule
    {
        
    }
}

Si vous tentez donc de lancer votre programme, le compilateur refusera de compiler le projet en vous précisant l'erreur suivante : « Base class is final. », qui signifie que la classe dont on essaie d'hériter est de type final et que notre héritage est donc contraire à cette définition de classe.

En résumé
  • L'héritage permet de créer une ou des nouvelles classes en utilisant le code d'une classe déjà existante.

  • Pour hériter d'une classe, il faut utiliser le mot-clé extends.

  • La portée protected est spécifique à l'héritage.

  • Le constructeur de la superclasse peut être appelé par la fonction super().

  • Dans le cas d'une relation par héritage, il est possible de substituer une sous-classe à une superclasse.

  • Le polymorphisme permet de redéfinir une méthode de la superclasse par l'intermédiaire du mot-clé override.

  • Les différents droits d'accès liés à la définition d'une classe sont public, internal, final et dynamic.

Exemple de certificat de réussite
Exemple de certificat de réussite