Aller au contenu principal

3) Héritage en C#

Objectifs de la séance

  • Transformer Personnage en classe abstraite
  • Créer des sous-classes concrètes : Guerrier, Mage, Archer
  • Utiliser virtual et override pour spécialiser les méthodes
  • Comprendre base.methode() et sealed

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}";
}
info

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

AspectC#Java
Héritageclass Guerrier : Personnageclass Guerrier extends Personnage
Constructeur parent: base(args)super(args)
Appel méthode parentbase.Methode()super.methode()
Méthode redéfinissablepublic virtual void M()public void m() (par défaut)
Forcer la redéfinitionpublic abstract void M()public abstract void m()
Interdire la redéfinitionpublic sealed override void M()public final void m()
Interdire l'héritagesealed class Cfinal 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


Comment déclare-t-on qu'une classe hérite d'une autre en C# ?


Quel mot-clé appelle le constructeur de la classe parent depuis un constructeur enfant ?


Une méthode abstraite peut-elle avoir un corps (des instructions) ?


Que se passe-t-il si une sous-classe concrète n'implémente pas une méthode abstraite ?


Que fait `sealed` appliqué à une classe ?


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.


Bonne pratique - protected pour les propriétés héritables

Les 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.


Bonne pratique - Appeler base(...) dans chaque constructeur enfant

Le 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.


Bonne pratique - Une sous-classe = une spécialisation

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

NotionRésumé
abstract classClasse 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.
overrideObligatoire 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.
sealedInterdit 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.