Aller au contenu principal

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 Guerrier doit fournir une méthode attaquer(), sinon PHP génère une erreur fatale : le contrat de l'interface Combattant n'est pas respecté.

Interface ou classe abstraite ?

Ces deux notions sont souvent confondues par les débutants. Voici l'essentiel à retenir :

InterfaceClasse abstraite
Implémentation de méthodesJamais (que des signatures)Possible (méthodes concrètes + abstraites)
PropriétésNon (uniquement des constantes)Oui
Nombre par classeUne classe peut en implémenter plusieursUne classe ne peut hériter que d'une seule
Mot-cléimplementsextends
info

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 Combattant n'a pas besoin de connaître sa classe réelle, uniquement qu'il sait attaquer().
  • Classes non liées : des classes sans aucun lien d'héritage (par exemple Guerrier et Tourelle) 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
}
info

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


Qu'est-ce qu'une interface en PHP ?


Quel mot-clé déclare une interface en PHP ?


Quel mot-clé utilise une classe pour respecter une interface ?


Une interface peut-elle contenir le code (le corps) d'une méthode ?


Combien de classes parentes une classe PHP peut-elle avoir avec 'extends' ?


Combien d'interfaces une classe PHP peut-elle implémenter avec 'implements' ?


Que se passe-t-il si une classe implémente une interface sans définir toutes ses méthodes ?


Quel est l'intérêt principal des interfaces pour des classes non liées par héritage ?



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.


Bonne pratique - Nommer une interface par un comportement

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.


Bonne pratique - extends avant implements

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().


Bonne pratique - Découpler avec les interfaces

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.

📌 Une solution