Les interfaces dans les langages de programmation orientés objet
Une interface est une construction de programmation, à la fois conceptuelle et syntaxique, servant à définir un ensemble précis de propriétés ou de comportements qu'un objet - souvent issu d'une classe - doit obligatoirement mettre en ouvre. Elle ne décrit pas la manière exacte dont ces comportements doivent être réalisés, mais elle impose qu'ils soient présents et utilisables. Autrement dit, l'interface agit comme un contrat que toute classe qui l'implémente s'engage à respecter.
Prenons un exemple concret : imaginons que notre application gère différents types de véhicules, comme une classe Car (voiture), une classe Scooter et une classe Truck (camion). Chacune de ces classes représente un type de véhicule avec ses propres caractéristiques et son mode de fonctionnement. Or, dans notre système, il est indispensable que tous ces véhicules soient capables d'exécuter une action appelée start_engine(), c'est-à-dire démarrer leur moteur.
Cependant, la façon exacte dont cette action est réalisée peut varier d'un type de véhicule à l'autre. Une voiture peut nécessiter un système de clef électronique, un scooter peut utiliser un simple bouton de démarrage, tandis qu'un camion peut demander une procédure plus complexe avec plusieurs étapes de sécurité. L'interface ne se préoccupe pas de ces détails techniques : elle se contente de spécifier que toute classe qui la respecte doit fournir une méthode start_engine() accessible et opérationnelle.
Ainsi, l'interface garantit une cohérence d'utilisation à travers différentes classes, tout en laissant à chacune la liberté d'implémenter les comportements de la manière la plus adaptée à ses besoins.
Syntaxe d'une interface
Une interface possède une syntaxe très simple ressemblant beaucoup à une définition de classe... l'interface publique XYZZY. À l'intérieur du {} de l'interface se trouve une liste de fonctions qui doivent être présentes dans tout objet censé «suivre» l'interface.
Les interfaces sont placées dans leurs propres fichiers, portant le même nom que l'interface (en majuscules) et se terminant par l'extension de langage courante (par exemple, «.as»). L'interface suivante serait placée dans un fichier «Vehicle.as».
Voici un exemple de l'interface Vehicle mentionnée ci-dessus (définition partielle seulement).
Voici les différences entre une interface et une classe :
- L'interface ne peut déclarer aucune VARIABLE. Une interface concerne les actions autorisées, et non les données ou leur implémentation.
- Le mot clef public n'est pas placé devant les prototypes de fonctions. Par définition, toutes les fonctions listées dans une interface doivent être publiques.
- Il n'y a pas de code après le prototype de fonction. Les {} habituels sont remplacés par un point-virgule.
Implémentation d'une interface
Pour indiquer à l'ordinateur qu'une nouvelle classe que vous écrivez remplira toutes les exigences (implémentera toutes les fonctions) d'une interface, vous devez utiliser le mot clef implements au même endroit que le mot clef extends.
Voici un exemple de classe Car implémentant la définition Vehicle :
N'oubliez pas : bien que toutes les fonctions de l'interface doivent être implémentées dans la classe, vous pouvez également créer toutes les autres fonctions que vous souhaitez pour cette classe.
Polymorphisme appliqué aux interfaces
Quel est l'intérêt d'une interface ? L'intérêt réside dans le fait qu'une fois que nous disposons de plusieurs classes implémentant l'interface, elles sont, d'un certain point de vue, équivalentes. Par exemple, imaginons que nous souhaitions créer une voiture et un camion, mais que notre programme ne souhaite que les démarrer et les conduire. Pour notre programme, ce ne sont que des véhicules.
Voici des exemples montrant comment écrire le code sans interfaces, avec des interfaces, puis avec des tableaux génériques de «véhicules».
Exemple sans interfaces :
Exemple avec interfaces :
Exemple d'utilisation d'un tableau et du polymorphisme :
- // Actions principales
- var vehicles : Array = new Array();
-
- vehicles.push( new Car() ); // Supposons que notre programme modélise un jeu ou une simulation de trafic
- vehicles.push( new Car() ); // Les voitures et les camions que nous modélisons peuvent être créés en
- vehicles.push( new Truck() ); // une question arbitraire. Normalement, nous devrions créer des
- vehicles.push( new Car() ); // tableaux pour voitures, camions,... Mais parce que ce sont tous des véhicules
- vehicles.push( new Truck() ); // nous pouvons les mettre dans un seul tableau de véhicules.
- vehicles.push( new Truck() );
- vehicles.push( new Truck() );
- vehicles.push( new Car() );
- vehicles.push( new Truck() );
- vehicles.push( new Car() );
- vehicles.push( new Bicycle() ); // supposons que le vélo soit aussi un véhicule...
- vehicles.push( new Car() );
-
-
- // Plus tard dans le programme, nous voulons démarrer toutes les voitures...
- // Nous pouvons utiliser la programmation GÉNÉRIQUE (peu importe qu'il s'agisse de voitures, de camions, de vélos,..., juste qu'il s'agisse de véhicules).
-
- for each (var item : Vehicle in vehicles ) {
- item.start_engine();
- item.drive();
- }
Le dernier exemple ci-dessus illustre le concept de polymorphisme. Le polymorphisme repose sur l'idée qu'au moment de la compilation (du codage), nous ne connaissons pas (et ne pouvons souvent pas connaître) le type d'objet réel contenu dans une variable. Dans le tableau « vehicules » ci-dessus, nous ne savons pas si «vehicules[i]» est une voiture, un camion, un vélo,... Dans les langages informatiques sans polymorphisme, nous ne pourrions rien faire avec ces objets.
Avec le polymorphisme, l'ordinateur mémorise la nature de chaque objet et, lorsque nous exécutons «item.start_engine()», il décide : si cet objet est un camion, il appelle «truck.start_engine()», s'il est une voiture, il appelle «car.start_engine()», et si cet objet est un XYZZY, il appelle «XYZZY.start_engine()».
Le polymorphisme permet au programmeur de gagner beaucoup de temps et d'efforts dans le codage de conditions « exceptionnelles ». L'ordinateur fait le travail pour nous, a) en se souvenant de ce qu'est réellement chaque objet, puis b) au moment de l'exécution, en appelant la fonction réelle associée à l'objet actuel.
Le type spécifique sous l'interface
Il est parfois nécessaire d'utiliser une fonction spécifique à un type sous-jacent. Par exemple, un camion-benne implémente Véhicule, mais possède également une fonction «raise_bed» déchargeant tout le contenu de l'arrière du camion. Dans le code suivant, le camion-benne est considéré par l'ordinateur comme un Véhicule; le code n'a donc pas accès à la fonction «raise_bed».
- var vehicle : Vehicle = new Dump_Truck();
-
- vehicle.start_engine(); // AUTORISÉ : le véhicule est un véhicule et possède donc la fonction start_engine
- vehicle.drive(); // AUTORISÉ : le véhicule est un véhicule et possède donc la fonction start_engine
-
- // Erreur de type au moment de la compilation
- vehicle.raise_bed(); // INTERDIT : alors qu'en réalité (à l'exécution) la variable véhicule
- // contiendra un Dump_Truck et que cela devrait donc être une opération légale,
- // le compilateur (au moment du programme) voit seulement que le véhicule est un
- // véhicule et que la classe Véhicule ne possède pas de fonction raise_bed.
Mais ! Vous dites : je (le programmeur) sais que ce véhicule est bien un Dump_Truck. Comment puis-je le dire à l'ordinateur ?
Mot clef as
Pour contourner la vérification de type de l'ordinateur, vous pouvez « prendre le contrôle » de votre programme et forcer l'ordinateur à traiter le contenu de la variable véhicule comme un Dump_Truck. Voici le code correct :
- var vehicle : Vehicle = new Dump_Truck();
-
- (vehicle as Dump_Truck).raise_bed(); // AUTORISÉ : ici le programmeur remplace la vérification de type de l'ordinateur.
Avertissement : Si la variable véhicule ne contient pas de Dump_Truck (erreur du programmeur), le programme entier plantera à ce stade.
Essayez de n'utiliser le mot-clé as que lorsque cela est absolument nécessaire. Lorsque nous ne l'utilisons pas, nous avons l'assurance de l'ordinateur que nos types sont corrects. Lorsque nous l'utilisons, nous n'avons que l'assurance du programmeur, et les programmeurs se trompent souvent.