Nous allons maintenant attaquer la deuxième partie du cours, traitant d'une notion très importante en Actionscript 3 : la Programmation Orientée Objet (ou POO). Appréhender cette notion sera peut-être difficile au début, mais vous finirez par tomber sous le charme de cette façon de programmer, c'est promis !
Cette partie sera également l'occasion de faire (enfin) un peu de pratique à l'occasion de quelques exercices que nous réaliserons ensemble.
Attachez vos ceintures, c'est parti ! La POO n'aura bientôt plus aucun secret pour vous !
Les notions-clés
Il était une fois… un objet
Vous avez sûrement une idée de ce qu'est un objet : dans la vie de tous les jours, nous en voyons, nous en touchons des centaines. Ils ont chacun une forme et remplissent une ou plusieurs tâches. On distingue rapidement plusieurs catégories d'objets qui se ressemblent : quand vous mettez la table, vous manipulez des objets (fourchettes, couteaux, cuillères, …) appartenant à la catégorie « couvert ». Bien sûr, vous pouvez avoir plusieurs objets de cette catégorie, voire plusieurs fourchettes identiques. Les objets de la catégorie « fourchette » font également partie de la catégorie « couvert », car les fourchettes ne sont en fait que des couverts particuliers.
La programmation orientée objet part de ces différentes observations pour introduire de nouveaux concepts en programmation, afin de rendre le développement de logiciels plus efficace et intuitif.
L'autre façon de programmer est appelée le procédural, où il n'y a grosso-modo que des variables, des fonctions et des instructions effectuées les unes à la suite des autres. La POO est différente par bien des aspects.
L'Objet
Prenons une de vos fourchettes.
Il s'agit d'un objet, ayant une forme particulière et une certaine utilité, que l'on peut retrouver dans les autres fourchettes. Un objet en programmation est également une entité possédant des propriétés que l'on peut retrouver sur d'autres objets de la même catégorie. Il ne s'agit ni d'une variable, ni d'une fonction, mais d'un mélange des deux : c'est un nouveau concept à part entière. Un objet peut donc contenir plusieurs variables (appelées attributs) et plusieurs fonctions (appelées méthodes). Ces variables et fonctions (appelées propriétés) sont profondément liées à l'objet qui les contient : les attributs stockent des informations sur cet objet. Par exemple, si mon objet « fourchette » possède un attribut « poids », cet attribut représente la masse de cette fourchette, et non la masse d'une autre fourchette posée à côté.
La Classe
Seulement, vous avez sur votre table non pas une mais dix fourchettes, quoique pas exactement identiques. En effet, cette fourchette-ci et plus légère que cette fourchette-là, donc son attribut « poids » est plus faible. Mais ces deux objets restent des fourchettes, malgré leur différence de poids. Fourchette
est donc la catégorie de ces objets, appelée classe.
On dit que les objets d'une classe sont des instances (ou occurrences) de cette classe.
Une classe décrit la nature de ses objets : c'est un schéma permettant de fabriquer et de manipuler plusieurs objets possédant les mêmes attributs et méthodes. Par exemple, la classe Fourchette
pourrait décrire les attributs « poids », « taille », « nombre de dents », etc.
Nous pourrions aussi créer une classe Couteau
pour les couteaux, et une classe Cuillère
pour les cuillères.
En Actionscript 3, les classes sont considérées comme des types, au même titre que les int
, Number
ou String
.
Un autre exemple
Une fois la table mise, vous tournez la tête et admirez à travers la fenêtre votre magnifique voiture toute neuve : ne serait-ce pas un objet de la classe Voiture
? Avec un objet aussi complexe, cette classe contient certainement plus d'attributs et de méthodes que la classe Fourchette
…
Voici quelques exemples d'attributs : « taille » (un grand classique), « marque », « couleur », « nombre de pneus », « nombre de sièges » ; et quelques exemples de méthodes : « verrouiller les portières », « démarrer le moteur », « accélérer », « freiner », « tourner à gauche », « tourner à droite »…
Vous apercevez un peu plus loin la voiture de votre voisin, garée en face. C'est également un objet de la classe Voiture
, avec une marque, une couleur, un nombre de pneus et de sièges, la possibilité d'accélérer, de freiner, de tourner à gauche ou à droite… Seulement elle n'a ni la même marque, ni la même couleur que la vôtre.
Ces deux objets sont des instances de la classe Voiture
.
L'encapsulation
Vous vous souvenez avoir ouvert le capot de votre voiture après l'avoir garé : vous avez intensément regardé ce qu'il y a à l'intérieur. Malgré votre immense effort de concentration, le fonctionnement du moteur, de tous ces câbles, circuits électriques et autres tuyaux vous échappe encore…
Fort heureusement, lorsque vous refermez le capot et montez dans votre voiture, vous oubliez toute cette mécanique trop compliquée, et vous conduisez à l'aide d'un volant, de pédales et d'un levier de boîte de vitesse. Grâce à eux, vous pouvez utiliser votre voiture sans être ingénieur en génie mécanique ou électronique !
Imaginez maintenant que vous écriviez une classe très complexe, qui permet de faire plein de choses incroyables ! Malheureusement, personne ne voudra de votre magnifique classe : qui serait assez fou pour se plonger dans votre code et tentez de le déchiffrer ?
En effet, votre code est un peu comme le bazar qui se trouve sous le capot de votre voiture. Mais vous êtes malins, et vous fournissez aux autres développeurs des méthodes très simples et faciles à comprendre, tout en cachant littéralement sous le capot ce qu'elles font, à l'instar du volant, des pédales et du frein à main. Ainsi, les programmeurs qui utiliseront votre classe (y compris vous-mêmes) auront un volant à disposition au lieu d'un moteur et vingt-et-un tuyaux. On appelle ce principe l'encapsulation.
L'héritage
Puis, votre regard se porte sur l'autoroute au loin : vous distinguez d'autres voitures. Mais des formes différentes se déplacent parmi elles : ce ne sont de toute évidence pas des voitures, mais plutôt des camions et des motos. Vous remarquez que ces objets comportent eux aussi des roues ; ils ont eux aussi une couleur et une marque ; ils peuvent eux aussi accélérer, freiner, tourner à gauche, tourner à droite… Ce seraient presque des objets de la classe Voiture
.
Mince alors ! Il va falloir refaire de nouvelles classes quasiment identiques…
Pourquoi ne pas faire une classe Véhicule
plus généraliste, qui décrirait les attributs et méthodes communs aux classes Voiture
, Camion
et Moto
? C'est une excellente idée !
Notre nouvelle classe Véhicule
décrit donc les attributs et les méthodes communs à tous les véhicules routiers, comme le nombre de roues et de sièges, la marque, la couleur, les méthodes « accélérer », « freiner », « tourner à gauche », « tourner à droite »… On l'appelle la classe mère (ou superclasse), par opposition aux classes Voiture
, Camion
et Moto
qui sont ses classes filles (ou sous-classes). On dit également que les classes Voiture
, Camion
et Moto
héritent de la classe Véhicule
.
Désormais, inutile de réécrire les attributs et méthodes de la classe Véhicule
dans la classe Voiture
, car cette dernière hérite des attributs et des méthodes de la classe Véhicule
!
Ainsi tous nos objets de la classe Voiture
sont également des objets de la classe Véhicule
, mais leur description dans la classe Voiture
est plus précise (par exemple, les voitures ont un coffre, alors que les camions ont une remorque).
L'un des intérêts de l'héritage est donc de pouvoir créer plusieurs classes différentes dérivant d'une classe mère, sans avoir à recopier les attributs et les méthodes communes dans chaque classe. C'est aussi une façon de penser et concevoir le programme de manière logique et cohérente.
Si nous revenions sur le premier exemple, la classe Fourchette
, ainsi que les classes Couteau
et Cuillère
, sont des sous-classes de la classe Couvert
!
Manipuler des objets : les chaînes de caractères
L'horrible secret du type String
Vous souvenez-vous des chaînes de caractères que nous avons vu dans la première partie ?
1 | var coucou:String = "Hello world!"; |
Regardez bien le type de la variable : String
… Il cache un terrible secret : il s'agit en réalité d'une classe !
Et oui ! En écrivant la ligne au-dessus, nous avons sans le savoir créé une instance de la classe String
. Et nous avons également utilisé des propriétés de cette classe !
Créer un objet
Tout d'abord, il nous faut apprendre à créer des objets. Il y a une syntaxe particulière à respecter :
1 | new String(); // Je créé un objet de la classe String (je l'ai d'ailleurs créé dans le vide, cette instruction ne servira à rien :D) |
Comme vous pouvez le voir, nous utilisons le mot-clé new
, suivi du nom de la classe de l'objet que nous voulons créer, et d'éventuels paramètres entre parenthèses (comme pour les fonctions). Par exemple, si nous voulions créer un objet de la classe String
contenant la chaîne "Hello world!"
, nous procéderions ainsi :
1 | var coucou:String = new String("Hello world!"); // Je créé un objet String contenant la chaîne "Hello world!" |
Mais, avant nous n'avions pas besoin de new
pour faire ça !
Effectivement, la classe String
possède une particularité : c'est un type de données. Cela signifie que les objets de cette classe sont un peu spéciaux : ils peuvent agir en tant que simple donnée (nombre, chaîne de caractères, etc.). Ainsi, nous pouvons nous passer de la syntaxe de création d'un objet :
1 | var coucou:String = "Hello world!"; |
revient à écrire :
1 | var coucou:String = new String("Hello world!"); |
Qu'en est-il de int
, uint
et Number
?
Et bien, figurez-vous que ce sont également des classes, qui fonctionnent de la même manière que la classe String
!
Il est impossible d'utiliser le mot-clé new
pour créer des objets des classes int
et uint
! C'est la raison pour laquelle elles commencent par une minuscule, pour bien marquer la différence avec les autres classes.
En effet, ces deux classes sont encore plus spéciales et n'utilisent qu'une seule syntaxe : celle que vous avez apprise dans la première partie du cours. Par contre, la classe Number
fonctionne tout à fait normalement, comme la classe String
:
1 2 3 4 5 6 7 8 9 10 11 | var entier:int = new int(-47); // INTERDIT ! ! ! var entier2:int = -42; // Autorisé var entierNonSigne:uint = new uint(47); // NOOON ! Tout sauf ça ! var entierNonSigne2:uint = 47; // Ouf ! var nombre:Number = new Number(3.1415); // Pas de problème var nombre2:Number = 3.1415; // Pour les flemmards :D var monNom:String = new String("Bryan"); // Je sens que je connais cette phrase... var ouSuisJe:String = "In the kitchen!"; // Argh ! |
Accéder aux propriétés d'un objet
Reprenons un peu plus de code que nous avons écrit dans la première partie :
1 2 3 | var coucou:String = new String("Hello world!"); trace("Cette chaîne contient " + coucou.length + " caractères."); // Affiche : Cette chaîne contient 13 caractères. |
et observons plus particulièrement cette expression : coucou.length
.
Nous avons utilisé la propriété length
de la classe String
, qui renvoie la longueur de la chaîne !
Décortiquons cette instruction :
- Tout d'abord, nous renseignons le nom de la variable contenant notre objet ; ici, il s'agit de
coucou
, variable que nous avons déclaré plus haut avec cette ligne :var coucou:String = "Hello world !";
. - Ensuite, nous utilisons le caractère point «
.
» pour signaler que nous allons utiliser un attribut ou une méthode de cet objet-là. - Enfin, nous spécifions quelle propriété nous voulons utiliser : ici, nous tapons donc
length
, la propriété de la classeString
qui nous intéresse.
Nous obtenons ainsi la longueur de l'objet « chaîne de caractères » contenu dans la variable coucou
! En français, cela donnerait quelque chose comme « Lis la propriété longueur de l'objet contenu dans ma variable coucou ».
Des pointeurs sous le capot
Plantons le décor
Prenons notre classe Voiture
, et supposons qu'elle a une propriété marque
de type String
qui contient le nom de la marque des voitures.
Créons un objet de la classe Voiture
et spécifions sa marque :
1 2 | var lienVoiture:Voiture = new Voiture(); // Je créé ma voiture... lienVoiture.marque = "Peugeot"; // ...de marque Peugeot. |
Déclarons une deuxième variable :
1 | var lienVoiture2:Voiture; // Je déclare une autre variable |
La variable n'est pas initialisée, elle contient donc null
.
Ensuite, procédons à une affectation :
1 | lienVoiture2 = lienVoiture; // C'est bien une affectation... Mais que fait-elle réellement ? |
Nous allons regarder de quelle marque sont les voitures (vous avez sûrement deviné ) :
1 2 | trace(lienVoiture.marque; // Affiche : Peugeot trace(lienVoiture2.marque); // Affiche : Peugeot |
Bien ! Maintenant, modifions la marque de lienVoiture2
:
1 2 3 | lienVoiture2.marque = "Renault"; // Nous pourrions également faire : lienVoiture2.marque = new String("Renault"); |
Enfin, regardons à nouveau la marque des voitures :
1 2 | trace(lienVoiture.marque); // Affiche : Renault trace(lienVoiture2.marque); // Affiche : Renault |
Horreur ! Les deux ont la même marque ! La réponse est simple : il s'agit du même objet ! Et oui, il n'y en a qu'un seul et unique !
Explications
Lorsque nous créons l'objet de classe Voiture
, il est stocké dans la mémoire vive de votre système. Ensuite, quand nous l'affectons à une variable, un lien vers cet objet est créé, puis stocké dans la variable. On dit alors que les variables sont des pointeurs : elles pointent du doigt l'objet qui leur est associé, afin que nous, programmeurs, puissions accéder à cet objet. D'ailleurs, vous aurez peut-être remarqué le nom précis que j'ai donné à mes variables : lienVoiture
et lienVoiture2
; n'est-ce pas évocateur ?
Bien évidemment, j'ai nommé les variables ainsi dans un but pédagogique : évitez d'appeler vos variables de cette façon, cela ne servirait à rien (et tous vos collègues vous dévisageraient d'un air bizarre).
Donc, lorsque nous écrivons notre affectation, nous nous contentons en réalité de recopier le pointeur vers l'objet initialement créé :
1 | lienVoiture2 = lienVoiture; // Recopions le pointeur de lienVoiture dans lienVoiture2 |
Ainsi, en utilisant les propriétés de l'objet de lienVoiture2
, nous utilisons également celles de l'objet de lienVoiture
. Logique : il s'agit du même objet !
Et si nous créions un autre objet de classe Voiture
?
Très bonne idée ! Créons un nouvel objet et affectons-le à la variable lienVoiture2
:
1 | lienVoiture2 = new Voiture(); // Un deuxième objet Voiture entre en scène ! |
Modifions sa marque :
1 | lienVoiture2.marque = "Citroën"; |
Et regardons la marque des deux voitures :
1 2 | trace(lienVoiture.marque); // Affiche : Renault trace(lienVoiture2.marque); // Affiche : Citroën |
Ouf ! Quel soulagement ! Nous avons bien deux objets distincts de la classe Voiture
!
En résumé
- Les objets contiennent des propriétés : les variables sont appelées attributs et les fonctions méthodes.
- Chaque objet est décrit par une classe : il s'agit d'une sorte de plan de construction des objets. On dit que les objets sont des instances (ou occurrences) de leur classe.
- L'encapsulation est un principe important en POO qui consiste à cacher le fonctionnement interne des classes, et à montrer seulement une interface simplifiée.
- L'héritage est un autre principe, tout aussi important, consistant à faire hériter des classes (dites classes filles) d'une classe mère.
- En Actionscript 3, tout est objet : tout ce que vous manipulerez, ce sera des objets.
- Le mot-clé
new
permet de créer des objets. - La plupart des variables sont en réalité des pointeurs (ou liens) vers des objets stockés en mémoire.