3) Héritage en C#
Objectifs de la séance
- Transformer
Personnageen classe abstraite - Créer des sous-classes concrètes :
Guerrier,Mage,Archer - Utiliser
virtualetoverridepour spécialiser les méthodes - Comprendre
base.methode()etsealed
Notions théoriques
Classe abstraite
Une classe abstraite définit un contrat commun mais ne peut pas être instanciée directement. Elle est le bon outil quand plusieurs types partagent un comportement commun tout en ayant des spécialisations.
abstract class Personnage
{
public string Nom { get; protected set; }
public int PointsDeVie { get; protected set; }
public int Force { get; protected set; }
protected Personnage(string nom, int pointsDeVie, int force)
{
Nom = nom;
PointsDeVie = pointsDeVie;
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 virtual string Decrire()
=> $"[{GetType().Name}] {Nom} — PV : {PointsDeVie} Force : {Force}";
}
abstract class : ne peut pas être instanciée (new Personnage(...) est interdit).
abstract void Attaquer(...) : pas de corps, les sous-classes sont obligées de l'implémenter.
virtual string Decrire() : a un corps, mais les sous-classes peuvent le redéfinir.
Sous-classes avec override
class Guerrier : Personnage
{
public int Armure { get; private set; }
public Guerrier(string nom, int pv, int force, int armure)
: base(nom, pv, force) // appel du constructeur parent
{
Armure = armure;
}
public override void Attaquer(Personnage cible)
{
Console.WriteLine($"{Nom} frappe {cible.Nom} avec son épée pour {Force} dégâts !");
}
public override string Decrire()
=> base.Decrire() + $" Armure : {Armure}";
}
sealed — bloquer l'héritage
Ajoutez sealed à une classe pour empêcher qu'on en hérite, ou à une méthode override pour qu'elle ne puisse plus être redéfinie dans des sous-classes ultérieures.
sealed class ArcherElite : Archer
{
// Personne ne peut hériter de ArcherElite
}
Tableau comparatif C# vs Java
| Aspect | C# | Java |
|---|---|---|
| Héritage | class Guerrier : Personnage | class Guerrier extends Personnage |
| Constructeur parent | : base(args) | super(args) |
| Appel méthode parent | base.Methode() | super.methode() |
| Méthode redéfinissable | public virtual void M() | public void m() (par défaut) |
| Forcer la redéfinition | public abstract void M() | public abstract void m() |
| Interdire la redéfinition | public sealed override void M() | public final void m() |
| Interdire l'héritage | sealed class C | final class C |
Exemple pratique
using System;
abstract class Personnage
{
public string Nom { get; protected set; }
public int PointsDeVie { get; protected set; }
public int Force { get; protected set; }
protected Personnage(string nom, int pv, int force)
{
Nom = nom; PointsDeVie = pv; Force = force;
}
public abstract void Attaquer(Personnage cible);
public virtual string Decrire()
=> $"[{GetType().Name}] {Nom} — PV : {PointsDeVie} Force : {Force}";
}
class Guerrier : Personnage
{
public int Armure { get; private set; }
public Guerrier(string nom, int pv, int force, int armure)
: base(nom, pv, force) => Armure = armure;
public override void Attaquer(Personnage cible)
=> Console.WriteLine($"{Nom} frappe {cible.Nom} avec son épée pour {Force} dégâts !");
public override string Decrire()
=> base.Decrire() + $" Armure : {Armure}";
}
class Mage : Personnage
{
public int Mana { get; private set; }
public Mage(string nom, int pv, int force, int mana)
: base(nom, pv, force) => Mana = mana;
public override void Attaquer(Personnage cible)
=> Console.WriteLine($"{Nom} lance un sort sur {cible.Nom} pour {Force * 2} dégâts magiques !");
public override string Decrire()
=> base.Decrire() + $" Mana : {Mana}";
}
class Archer : Personnage
{
public int Portee { get; private set; }
public Archer(string nom, int pv, int force, int portee)
: base(nom, pv, force) => Portee = portee;
public override void Attaquer(Personnage cible)
=> Console.WriteLine($"{Nom} tire une flèche sur {cible.Nom} à {Portee}m pour {Force} dégâts !");
}
// Programme
Guerrier g = new Guerrier("Kael", 100, 15, 8);
Mage m = new Mage ("Aria", 80, 10, 50);
Archer a = new Archer ("Theron", 90, 12, 30);
Console.WriteLine(g.Decrire());
Console.WriteLine(m.Decrire());
Console.WriteLine(a.Decrire());
Console.WriteLine();
g.Attaquer(m);
m.Attaquer(a);
a.Attaquer(g);
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Étape 1 — Transformer Personnage en classe abstraite
Ajoutez abstract à la déclaration de la classe et transformez Attaquer en méthode abstraite.
protected pour les propriétés héritablesLes propriétés Nom, PointsDeVie et Force sont protected set pour que les sous-classes puissent les modifier (par exemple, perdre des points de vie) sans les exposer à l'extérieur. private set bloquerait également les sous-classes.
Étape 2 — Créer la classe Guerrier
La classe Guerrier hérite de Personnage, a une propriété Armure et implémente Attaquer.
base(...) dans chaque constructeur enfantLe constructeur enfant doit appeler le constructeur parent avec : base(...). C'est obligatoire si la classe parent n'a pas de constructeur sans paramètres. Cela garantit que l'initialisation commune se fait toujours, sans dupliquer le code.
Étape 3 — Créer Mage et Archer, puis tester
Créez les classes Mage (avec propriété Mana, dégâts = Force * 2) et Archer (avec propriété Portee). Instanciez un personnage de chaque type et faites-les s'attaquer mutuellement.
Chaque sous-classe ajoute uniquement ce qui la différencie du parent (Mana, Portee, Armure) et redéfinit uniquement les comportements spécifiques (Attaquer). Le reste est hérité. C'est le principe de la réutilisation par héritage.
📌 Une solution
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
abstract class | Classe non instanciable définissant un contrat commun. |
abstract void M() | Méthode sans corps — les sous-classes doivent l'implémenter. |
virtual void M() | Méthode avec corps — les sous-classes peuvent la redéfinir. |
override | Obligatoire pour redéfinir une méthode virtual ou abstract. |
: base(args) | Appel du constructeur parent (équivalent de super(args) en Java). |
base.Methode() | Appel de la méthode parent dans une redéfinition. |
sealed | Interdit l'héritage ultérieur (équivalent de final en Java). |
Aperçu de la prochaine séance
Dans la prochaine séance, vous allez ajouter une interface ISoignable au projet. Le Mage pourra soigner d'autres personnages en plus d'attaquer. Vous découvrirez le pattern matching (is, switch avec cases typés) pour traiter des listes hétérogènes de personnages, et vous verrez comment C# gère le polymorphisme au travers d'une List<Personnage> mixant Guerriers, Mages et Archers.