Aller au contenu principal

3) Guerriers et Mages

Héritage — Guerriers et Mages

Notre jeu RPG a maintenant des personnages génériques. Mais un guerrier n'attaque pas de la même façon qu'un mage ! C'est ici qu'intervient l'héritage : il permet de créer des classes spécialisées qui partagent une structure commune.

Objectifs de la séance

  • Transformer Personnage en classe abstraite avec une méthode abstraite
  • Créer les classes Guerrier et Mage qui héritent de Personnage
  • Utiliser @Override pour spécialiser le comportement de chaque sous-classe
  • Comprendre la différence entre classe abstraite et classe concrète

Notions théoriques

La classe abstraite

Une classe abstraite est une classe incomplète : elle définit la structure commune mais délègue certains comportements aux sous-classes. On ne peut pas l'instancier directement.

public abstract class Personnage {
private String nom;
private int pointsDeVie;
private int force;

public Personnage(String nom, int pointsDeVie, int force) {
this.nom = nom;
this.pointsDeVie = pointsDeVie;
this.force = force;
}

// Méthode abstraite : chaque sous-classe DOIT l'implémenter
public abstract void attaquer(Personnage cible);

// Méthode concrète : partagée par toutes les sous-classes
public void afficher() {
System.out.println("Nom : " + nom + " | PV : " + pointsDeVie + " | Force : " + force);
}

// Getters
public String getNom() { return nom; }
public int getPointsDeVie() { return pointsDeVie; }
public int getForce() { return force; }
public void setPointsDeVie(int pointsDeVie) { this.pointsDeVie = pointsDeVie; }
}
info

Le mot-clé abstract devant la méthode signifie qu'elle n'a pas de corps dans la classe abstraite. Chaque classe qui hérite de Personnage doit obligatoirement fournir une implémentation.

L'héritage avec extends

Pour créer une sous-classe, on utilise extends :

public class Guerrier extends Personnage {

public Guerrier(String nom, int pointsDeVie, int force) {
super(nom, pointsDeVie, force); // appel du constructeur parent
}

@Override
public void attaquer(Personnage cible) {
int degats = getForce() * 2;
cible.setPointsDeVie(cible.getPointsDeVie() - degats);
System.out.println(getNom() + " attaque " + cible.getNom() + " avec " + degats + " dégâts physiques !");
}
}
attention

super(...) doit toujours être la première instruction dans le constructeur d'une sous-classe. Il appelle le constructeur de la classe parente pour initialiser les attributs hérités.

@Override

L'annotation @Override indique au compilateur que vous redéfinissez une méthode de la classe parente. Si vous faites une faute de frappe dans le nom de la méthode, Java signale une erreur. C'est un filet de sécurité important.

La classe Mage avec mana

Le Mage possède un attribut supplémentaire : mana. Son attaque coûte du mana et inflige force * 1.5 dégâts.

public class Mage extends Personnage {
private 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 >= 10) {
int degats = (int) (getForce() * 1.5);
cible.setPointsDeVie(cible.getPointsDeVie() - degats);
mana -= 10;
System.out.println(getNom() + " lance un sort sur " + cible.getNom() + " : " + degats + " dégâts magiques !");
} else {
System.out.println(getNom() + " n'a plus assez de mana !");
}
}

public int getMana() { return mana; }
}

Exemple pratique

public class Main {

public static void main(String[] args) {
Guerrier aragorn = new Guerrier("Aragorn", 150, 30);
Mage gandalf = new Mage("Gandalf", 100, 20, 50);

System.out.println("=== Combat ===");
aragorn.attaquer(gandalf);
gandalf.attaquer(aragorn);

aragorn.afficher();
gandalf.afficher();
}
}

Sortie attendue :

=== Combat ===
Aragorn attaque Gandalf avec 60 dégâts physiques !
Gandalf lance un sort sur Aragorn : 30 dégâts magiques !
Nom : Aragorn | PV : 120 | Force : 30
Nom : Gandalf | PV : 40 | Force : 20

Test de mémorisation/compréhension


Peut-on instancier directement une classe abstraite ?


Quel mot-clé permet à une classe d'hériter d'une autre ?


À quoi sert l'annotation @Override ?


Comment appelle-t-on le constructeur de la classe parente ?


Une méthode abstraite dans une classe abstraite a-t-elle un corps ?


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

Vous allez modifier Personnage pour la rendre abstraite, puis créer Guerrier et Mage.

Étape 1 — Rendre Personnage abstraite

Modifiez la classe Personnage pour la déclarer abstraite et ajoutez la méthode abstraite attaquer(Personnage cible).


Bonne pratique - Classe abstraite pour définir un contrat

Rendre Personnage abstraite garantit que tout type de personnage aura une méthode attaquer(). Vous ne pouvez pas oublier de l'implémenter : Java refusera de compiler si vous créez une sous-classe sans implémenter toutes les méthodes abstraites.

Étape 2 — Créer la classe Guerrier

Créez Guerrier.java qui hérite de Personnage. Implémentez attaquer() : les dégâts sont égaux à force * 2.


Bonne pratique - Utiliser les getters depuis les sous-classes

Les attributs de Personnage sont private. Depuis Guerrier, vous ne pouvez pas écrire force directement : il faut utiliser getForce(). Cela préserve l'encapsulation et garantit que les règles de validation du setter sont respectées.

Étape 3 — Créer la classe Mage

Créez Mage.java qui hérite de Personnage et ajoute un attribut mana. Implémentez attaquer() avec les dégâts de force * 1.5 et un coût de 10 mana.


Bonne pratique - Ajouter uniquement les attributs spécifiques

La classe Mage n'a besoin de déclarer que mana. Les attributs nom, pointsDeVie et force sont hérités de Personnage. C'est tout l'intérêt de l'héritage : ne pas dupliquer du code.

📌 Une solution