Les interfaces
Notions théoriques
Après l'héritage, découvrons un second mécanisme qui permet à des classes de partager un comportement commun : l'interface.
Qu'est-ce qu'une interface ?
Définition : Une interface est un contrat : elle liste des méthodes que toute classe qui l'implémente s'engage à définir, sans jamais fournir le code de ces méthodes.
Explication :
Une interface ne contient aucune implémentation : uniquement des signatures de méthodes (nom, paramètres), sans corps ni accolades de code. En PHP, on la déclare avec le mot-clé interface, et une classe s'engage à respecter ce contrat avec le mot-clé implements.
Exemple :
<?php
interface Combattant {
public function attaquer();
}
class Guerrier implements Combattant {
private $force;
public function attaquer() {
print("Le guerrier attaque avec une force de {$this->force} points.\n");
}
}
?>
La classe
Guerrierdoit fournir une méthodeattaquer(), sinon PHP génère une erreur fatale : le contrat de l'interfaceCombattantn'est pas respecté.
Interface ou classe abstraite ?
Ces deux notions sont souvent confondues par les débutants. Voici l'essentiel à retenir :
| Interface | Classe abstraite | |
|---|---|---|
| Implémentation de méthodes | Jamais (que des signatures) | Possible (méthodes concrètes + abstraites) |
| Propriétés | Non (uniquement des constantes) | Oui |
| Nombre par classe | Une classe peut en implémenter plusieurs | Une classe ne peut hériter que d'une seule |
| Mot-clé | implements | extends |
Une classe abstraite répond à la question « qu'est-ce que c'est ? » (une relation « est-un », par héritage). Une interface répond à la question « que sait-il faire ? » (un comportement partagé, sans lien de parenté).
Nous approfondirons les classes abstraites dans la prochaine séance : retenez pour l'instant cette distinction essentielle.
Implémenter plusieurs interfaces
Explication : Une classe ne peut hériter que d'une seule classe parente, mais elle peut implémenter plusieurs interfaces à la fois, séparées par des virgules.
Exemple :
<?php
interface Combattant {
public function attaquer();
}
interface Soignant {
public function soigner(Combattant $cible);
}
class Guerisseur implements Combattant, Soignant {
public function attaquer() {
print("Le guérisseur attaque à contrecœur.\n");
}
public function soigner(Combattant $cible) {
print("Le guérisseur soigne son allié.\n");
}
}
?>
Pourquoi utiliser des interfaces ?
- Polymorphisme : des objets de classes différentes, mais qui implémentent la même interface, peuvent être traités de façon identique par le reste du programme.
- Découplage : le code qui utilise un
Combattantn'a pas besoin de connaître sa classe réelle, uniquement qu'il saitattaquer(). - Classes non liées : des classes sans aucun lien d'héritage (par exemple
GuerrieretTourelle) peuvent partager un même comportement grâce à une interface commune.
Exemple pratique
Reprenons notre jeu de combat. Nous avons déjà les classes Guerrier et Magicien, héritières de Personnage. Ajoutons maintenant une classe Tourelle, qui n'a aucun lien de parenté avec Personnage, mais qui peut tout de même participer aux combats grâce à l'interface Combattant.
interface Combattant {
public function attaquer();
}
class Guerrier extends Personnage implements Combattant {
private $force;
public function attaquer() {
print($this->getNom() . " attaque avec une force de " . $this->force . " points.\n");
}
}
class Magicien extends Personnage implements Combattant {
private $magie;
public function attaquer() {
print($this->getNom() . " lance un sort avec un niveau de magie de " . $this->magie . " points.\n");
}
}
// Tourelle n'hérite pas de Personnage, mais implémente le même contrat
class Tourelle implements Combattant {
public function attaquer() {
print("La tourelle tire une rafale automatique.\n");
}
}
// Toutes ces classes peuvent être manipulées de la même façon
$combattants = [new Guerrier(), new Magicien(), new Tourelle()];
foreach ($combattants as $combattant) {
$combattant->attaquer(); // Polymorphisme : peu importe la classe réelle
}
Grâce au polymorphisme permis par l'interface Combattant, la boucle foreach peut appeler attaquer() sur n'importe quel objet du tableau, sans se soucier de sa classe exacte.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Nous allons rendre notre jeu de combat plus flexible en introduisant une interface Combattant, respectée à la fois par des personnages (qui héritent de Personnage) et par une nouvelle classe Tourelle, totalement indépendante.
Dans le répertoire classes du répertoire jeu_combat (des séances précédentes) :
Étape 1 — Déclarer l'interface Combattant
Créez le fichier Combattant.php qui déclare l'interface Combattant avec une seule méthode attaquer(), sans paramètre.
Nommez vos interfaces d'après une capacité ou un comportement (Combattant, Soignant) plutôt que d'après un objet concret. Cela rend explicite le contrat qu'elles imposent, indépendamment de la classe qui l'implémente.
Étape 2 — Faire implémenter l'interface par Guerrier et Magicien
Modifiez la déclaration de la classe Guerrier pour qu'elle continue d'hériter de Personnage, tout en implémentant désormais l'interface Combattant.
Quand une classe hérite d'une classe parente et implémente une ou plusieurs interfaces, extends se place toujours avant implements sur la ligne de déclaration.
Étape 3 — Créer une classe Tourelle indépendante
Créez une classe Tourelle qui n'hérite d'aucune classe, mais qui implémente l'interface Combattant avec sa propre version de attaquer().
Utilisez une interface pour permettre à des classes sans lien de parenté (ici Tourelle, qui n'est pas un Personnage) de participer au même comportement. C'est un moyen puissant de réduire les dépendances rigides entre vos classes.