Aller au contenu principal

Les interfaces

Notions théoriques

Qu'est-ce qu'une interface ?

Une interface est un contrat que des classes s'engagent à respecter. Elle définit ce qu'une classe doit savoir faire, sans préciser comment elle le fait.

Contrairement à l'héritage (relation "est-un"), une interface modélise une relation "peut faire" ou "est capable de" :

  • Un Mage peut se soignerMage implements Soignable
  • Un Guerrier peut attaquerGuerrier implements Attaquable
// Déclaration d'une interface
public interface Soignable {
void soigner(int points); // méthode abstraite implicitement
}

Déclarer une interface

Le mot-clé interface remplace class. Toutes les méthodes d'une interface sont publiques et abstraites par défaut : inutile d'écrire public abstract.

public interface Soignable {
void soigner(int points); // implicitement : public abstract void soigner(int points);
}

public interface Attaquable {
void attaquer(Personnage cible);
}

Implémenter une interface avec implements

Une classe déclare qu'elle respecte le contrat d'une interface avec le mot-clé implements. Elle doit alors fournir un corps à toutes les méthodes de l'interface, avec @Override.

public class Mage extends Personnage implements Soignable {

@Override
public void soigner(int points) {
if (mana >= 10) {
pointsDeVie += points;
mana -= 10;
System.out.println(nom + " se soigne de " + points + " PV !");
} else {
System.out.println(nom + " n'a pas assez de mana pour se soigner.");
}
}
}

Plusieurs interfaces à la fois

Java n'autorise qu'un seul héritage de classe (extends), mais une classe peut implémenter autant d'interfaces que nécessaire.

public class Paladin extends Personnage implements Soignable, Attaquable {

@Override
public void soigner(int points) { /* ... */ }

@Override
public void attaquer(Personnage cible) { /* ... */ }
}
Une interface peut aussi hériter d'autres interfaces

De la même façon qu'une classe peut implémenter plusieurs interfaces, une interface peut hériter de plusieurs autres interfaces avec extends :

// Combattant regroupe les contrats Soignable et Attaquable
public interface Combattant extends Soignable, Attaquable {
int getNiveau(); // méthode supplémentaire propre à Combattant
}

Toute classe qui implémente Combattant doit alors fournir soigner(), attaquer() et getNiveau().

L'interface comme type

Une interface peut être utilisée comme type de variable. C'est très utile pour écrire du code générique.

Soignable s = new Mage("Elara", 80, 10, 100);
s.soigner(20); // fonctionne car Mage implements Soignable

// Mais :
s.attaquer(cible); // ERREUR : le type Soignable ne connaît pas attaquer()

Méthodes default (Java 8+)

Depuis Java 8, une interface peut contenir des méthodes avec un corps grâce au mot-clé default. Cela permet d'ajouter de nouveaux comportements sans casser les classes existantes.

public interface Soignable {
void soigner(int points);

// Méthode default : implémentation fournie, les classes peuvent la redéfinir
default void soignerCompletement() {
soigner(9999);
System.out.println("Soin total !");
}
}

Méthodes static dans une interface

Une interface peut aussi contenir des méthodes statiques utilitaires.

public interface Soignable {
void soigner(int points);

static int calculerSoin(int niveau) {
return niveau * 10;
}
}

// Appel :
int soin = Soignable.calculerSoin(5); // → 50

Constantes dans une interface

Les attributs déclarés dans une interface sont implicitement public static final (des constantes).

public interface Attaquable {
int DEGATS_MINIMUM = 1; // implicitement : public static final int DEGATS_MINIMUM = 1;

void attaquer(Personnage cible);
}

Exemple pratique

// Fichier : Soignable.java
public interface Soignable {
void soigner(int points);

default void soignerCompletement() {
soigner(Integer.MAX_VALUE);
System.out.println("Soin total appliqué !");
}
}

// Fichier : Attaquable.java
public interface Attaquable {
int DEGATS_MINIMUM = 1;
void attaquer(Personnage cible);
}

// Fichier : Mage.java
public class Mage extends Personnage implements Soignable {
int mana;

public Mage(String nom, int pointsDeVie, int force, int mana) {
super(nom, pointsDeVie, force);
this.mana = mana;
}

@Override
public void attaquer(Personnage cible) {
if (mana >= 15) {
int degats = Math.max(force * 3, Attaquable.DEGATS_MINIMUM);
cible.pointsDeVie -= degats;
mana -= 15;
System.out.println(nom + " lance un sort ! Dégâts : " + degats);
} else {
cible.pointsDeVie -= force;
System.out.println(nom + " frappe faiblement.");
}
}

@Override
public void soigner(int points) {
if (mana >= 10) {
pointsDeVie += points;
mana -= 10;
System.out.println(nom + " se soigne de " + points + " PV ! PV actuels : " + pointsDeVie);
} else {
System.out.println(nom + " n'a pas assez de mana pour se soigner.");
}
}
}

// Fichier : Main.java
public class Main {
public static void main(String[] args) {
Mage mage = new Mage("Elara", 80, 12, 50);

// Utiliser le Mage via l'interface Soignable
Soignable soigneur = mage;
soigneur.soigner(30);

// Utiliser la méthode default
soigneur.soignerCompletement(); // méthode default de l'interface
}
}
info

Soignable soigneur = mage; illustre l'utilisation de l'interface comme type. La variable soigneur ne "voit" que les méthodes définies dans Soignable. C'est le polymorphisme par interface.

Test de mémorisation/compréhension


Quel mot-clé permet à une classe d'implémenter une interface ?


Combien d'interfaces une classe Java peut-elle implémenter ?


Quel mot-clé Java 8 permet d'ajouter une méthode avec un corps dans une interface ?


Dans une interface, les méthodes sans corps sont implicitement :


Peut-on déclarer une variable de type interface ?


Quelle est la nature des attributs déclarés dans une interface ?


TP pour réfléchir et résoudre des problèmes

Vous allez créer deux interfaces Soignable et Attaquable, puis les implémenter dans les classes du RPG.

Étape 1 — Créer l'interface Soignable

Créez le fichier Soignable.java avec la méthode soigner(int points) et une méthode default afficherPV().


Bonne pratique - Interface à responsabilité unique

Une interface doit représenter un seul comportement cohérent. Préférez plusieurs petites interfaces (Soignable, Attaquable) à une grande interface fourre-tout. C'est le principe de ségrégation des interfaces (Interface Segregation Principle).

Étape 2 — Implémenter Soignable dans Mage

Modifiez Mage pour implémenter l'interface Soignable. La méthode soigner() coûte 10 de mana et restaure les points de vie.


Bonne pratique - @Override sur les implémentations d'interface

L'annotation @Override s'applique aussi quand on implémente une méthode d'interface, pas seulement quand on redéfinit une méthode héritée. Elle garantit que la signature correspond exactement à celle de l'interface.

Étape 3 — Utiliser l'interface comme type dans Main

Dans Main, déclarez une variable de type Soignable et appelez la méthode soigner().


Bonne pratique - Coder contre l'interface, pas contre l'implémentation

Écrire Soignable soigneur = new Mage(...) plutôt que Mage soigneur = new Mage(...) est une bonne pratique : le code qui utilise soigneur ne dépend pas de la classe concrète. Si vous remplacez Mage par Pretre (qui implémente aussi Soignable), le reste du code reste inchangé.

📌 Une solution