2. POO - Héritage
Après cette quête, tu pourras concevoir des classes en partant d'autres classes
Objectifs
- Comprendre la notion d'héritage
- Connaître la syntaxe en C#
- Aborder le polymorphisme
- Utiliser des classes abstraites
- Surcharger des méthodes
Un cours de biologie pas comme les autres

Félicitations ! Tu viens d’intégrer une classe très sélective. Il s'agit d'un cours de biologie, mais pas un cours de biologie comme les autres : c'est le début de la modélisation de modèles biologiques grâce à l'utilisation de la programmation orientée objet.
Tu es prêt ? C'est parti !
Une classe-mère

Chaque espèce est classifiée selon certains critères d'une autre espèce qui la précède. Par exemple, les reptiles et les oiseaux sont des survivances des dinosaures. Ils partagent des points en commun avec ces ancêtres imposants. Chaque enfant a au moins un géniteur (toutes espèces comprises) et l'enfant hérite des caractéristiques de son ou ses géniteur.s. Certaines spécificités de son géniteur persistent et d'autres apparaissent, car les espèces s'adaptent à leur environnement et développent leurs propres réflexes : c’est la loi de l'évolution.
Lorsque tu entends le mot "héritage", qu'est-ce qui te vient à l'esprit ?
Un patrimoine transmis par un membre de la famille après sa mort ?
C'est effectivement la première définition qui vient à l'esprit d'un profane. Et ce n'est vraiment pas loin de la réalité par rapport à son application en programmation informatique orientée objet ! On enlève la notion de mort, mais on garde celle de transmission d'une personne à l'autre. En effet, l'acte d'hériter en programmation peut se définir comme étant une transmission d'une classe à l'autre d'attributs et de méthodes.
En clair, l'héritage permet à une classe de transmettre ce qu'elle contient à une autre. La classe-mère est la classe dont les classes-filles (ou enfants) héritent. Un peu à la manière d'un arbre généalogique où chaque enfant hérite d'un parent, l'enfant garde le même nom de famille et héritera peut-être de la fortune familiale (s’il y en a une). L'enfant possède à coup sûr des caractéristiques venant de ses parents : couleur des yeux, des cheveux, taille, caractère ... Mais il est quand même différent, puisqu'il a un prénom généralement différent et d'autres attributs dus à la fusion des gènes de leurs parents et à la mutation possible desdits gènes pendant une grossesse. Cependant, il reste distinct de ses parents.
Qu'est-ce que ça a à voir avec la programmation ?
Une classe-fille héritant de certains attributs et méthodes, elle possède des caractéristiques de sa classe-mère. Imaginons le cas d'un animal. Un animal peut marcher et manger. En revanche, un chat n'aboie pas et un chien ne miaule pas. Voici deux actions (méthodes) différentes entres elles - d'un animal à l'autre. Et pourtant, les deux animaux partagent des caractéristiques communes : un chat et un chien ont quatre pattes, des poils, un museau, ils mangent, boivent et se reproduisent entre eux. Ainsi, un chien et un chat sont tous les deux des animaux : la classe-mère des classes Cat et Dog pourrait être Animal qui regrouperait les caractéristiques qu'ils ont en commun.
L'héritage permet de réduire la duplication de code, fournir un code maintenable et, surtout et toujours, de fournir le modèle logique le plus fidèle possible à la réalité. Ainsi, l'héritage est une des plus grandes forces de la nature, alors les programmeurs ont décidé de s'en servir !
Comment on fait alors pour partager des attributs et méthodes d'une classe en partant d'une autre ?
Regarder jusque 1:30
La visibilité protégée
Tu connais deux types de visibilité pour tes attributs et méthodes : publique et privée. La visibilité publique donne accès à la méthode ou à l'attribut à l'extérieur de l'objet. La visibilité privée est le strict opposé : l'accessibilité des méthodes et attributs ne peut être faite qu'à l'intérieur de l'objet.
Sais-tu qu'il existe un troisième niveau de visibilité pour tes attributs et méthodes ? Et il a de génial qu'il est conçu uniquement pour utiliser l'héritage. Ce troisième niveau de visibilité permet à un attribut ou une méthode d'être manipulé.e dans les classe-filles mais pas en dehors des objets instanciés, ce que ne permet pas la portée privée qui masque un attribut ou une méthode à tout le programme, sauf l'instance de la classe en question. La portée publique rend aussi accessible aux classes-filles mais l'attribut ou la méthode concernée est accessible en-dehors de l'instance de l'objet. Ce qui rend nécessaire l'existence d'un troisième niveau de visibilité qui partage les composantes d'une classe à une autre, sans exposer à l'extérieur ce qu'elles contiennent.
En C#, si on veut marquer une composante de classe publique, on utilise le mot-clé public. Si on veut marquer une composante de classe privée, on utilise le mot-clé private. Je te donne par conséquent le temps de réfléchir à comment on pourrait qualifier une composante de classe protégée.
protected?

Eh bien oui ! En informatique nous sommes des gens simples et essayons de le rester malgré la complexité environnante. Donc, on déclare une composante de classe ayant une visibilité protégée avec le mot-clé protected.
Ainsi :
namespace MyProgram
{
public class Animal
{
protected short legsCount;
protected bool hairy;
private string _name;
public Animal(string name, short legsCount, bool hairy)
{
_name = name;
this.legsCount = legsCount;
this.hairy = hairy;
}
public void Eat()
{
Console.WriteLine("I eat");
}
public string GetName()
{
return _name;
}
}
}
Le code ci-dessus :
-
Déclare une classe :
-
Avec un attribut protégé qui représente le nombre de pattes de l'animal
-
Avec un attribut protégé qui représente la pilosité de l'animal
-
Avec un attribut privé qui représente le nom de l'animal
-
Avec une méthode publique qui représente l'action de manger
-
Avec un accesseur en lecture pour l'attribut privé
name
-
Comment je me sers de tout ça, maintenant, pour créer un programme au design satisfaisant ?
Une classe abstraite
Certains de nos ancêtres n'existent plus. Les humains descendent de certaines espèces de singes qui ont aujourd'hui disparu de la surface de la Terre. Pourtant, on utilise toujours leurs caractéristiques pour qualifier les humains. Ce sont en quelque sorte des espèces "abstraites". Elles n'existent pas à proprement parler, mais nous partageons leurs caractéristiques.
Afin de permettre au programmeur de penser en-dehors de la boîte, on a inventé le modèle objet qui permet de manipuler des entités ayant des comportements spécifiques à leur nature. On s'est rendu compte, en observant la nature, que certains principes existent dans l'esprit humain mais pas ailleurs. On dit alors que ce concept est abstrait. Puisque la programmation représente le monde réel de manière abstraite, certaines classes ont des comportements spécifiques mais n'existent pas. Elles servent de base à une autre classe partageant ses caractéristiques et comportements. Les concepteurs de la POO ont inventé le concept de classe abstraite . Une classe abstraite est une classe qui ne peut pas être instanciée.
Mais à quoi ça sert une classe qu'on ne peut pas instancier ?
La destinée des classes abstraites est d'être dérivée par un autre type qui hérite de ses attributs publics et protégés. Ainsi, il est interdit de directement créer une instance de classe abstraite car la seule utilité de cette classe est de servir de base aux classes-filles qui dériveront de leur classe-mère abstraite.
Ainsi, afin de programmer de manière fluide et avec une représentation claire de la réalité, il peut être très utile de regrouper des caractéristiques communes à des types d'objets différents entre eux, dans une classe-mère abstraite.
Reprenons l'exemple de l'animal. Comme nous en avons convenu lors de l'étape précédente : un chat n'aboie pas et un chien ne miaule pas. Pourtant, tous les deux ont des poils, quatre pattes et mangent. Ce sont donc deux entités qui partagent des caractéristiques communes mais pas exclusivement. Par conséquent, on pourrait regrouper les caractéristiques communes d'un chien et d'un chat à l'intérieur d'une classe abstraite. Cette classe abstraite regroupe les trois caractéristiques qu'ont un chien et un chat en commun :
-
Ils ont des poils
-
Ils ont quatre pattes
-
Ils mangent
Voici un code qui pourrait représenter nos deux amis chien et chat :
namespace MyProgram
{
public abstract class Animal
{
protected short _legsCount;
protected bool _hairy;
protected string _name;
public Animal(string name, short legsCount, bool hairy)
{
_name = name;
_legsCount = legsCount;
_hairy = hairy;
}
public void Eat()
{
Console.WriteLine("I eat");
}
public string GetName()
{
return _name;
}
}
public class Dog : Animal
{
public Dog(string name) : base(name, 4, true)
{ }
public void Bark()
{
System.Console.WriteLine("Bark");
}
}
public class Cat : Animal
{
public Cat(string name) : base(name, 4, true)
{ }
public void Meow()
{
System.Console.WriteLine("Meow");
}
}
}
Intéressons-nous aux détails de notre code :
-
Une classe abstraite
Animalest définie. Elle contient des attributs communs à un chien et un chat :_legsCount,Eat()et_hairy:- Le mot-clé abstract est utilisé dans la déclaration de la classe pour spécifier qu'elle ne peut pas être instanciée.
-
Une class
Dogest définie. Notre chien a donc quatre pattes, des poils et un nom. Seul le nom est accessible en modification. Si une des autres caractéristiques (nombre de pattes, poils) est modifiée dans une instance, on ne pourrait plus la qualifier en tant queChienmais animal mutant.-
Le constructeur parent est appelé pour instancier l'objet
-
Un double point suivi du nom de la classe abstraite transmet les caractéristiques de la classe abstraite à sa classe enfant :
Dog -
Une méthode
Bark()fait aboyer le chien
-
-
Même chose qu'au-dessus pour le chat :
- Une méthode
Meow()fait miauler le chat
- Une méthode
Cette représentation simpliste nous permet d'établir un modèle de présentation de la réalité qui s’approche de la vérité. Cet exemple nous permet de comprendre qu'il est possible qu'une classe hérite des spécificités d'une autre, mais qu'elle doit proposer des comportements différents de l'autre. En effet, une classe dérivée (qui hérite de) possède une différence de comportement par rapport à sa mère, mais pas assez pour s'en défaire, car elle possède d'autres spécificités en commun.

Le polymorphisme

Qu'est-ce que peut bien vouloir dire ce mot ? Polymorphisme ?
Définition Wikipédia:
Le polymorphisme, du grec πολύμορφος / polúmorphos (« multiforme »), caractérise la capacité à se présenter sous différentes formes.
Pour parler étymologie, "poly" est une racine qui signifie "plusieurs". "morphisme" en grec signifie "forme". Donc le polymorphisme est, comme le dit Wikipédia, la capacité à se présenter sous différentes formes.
Mais en quoi la capacité à apparaître sous plusieurs formes nous concerne-t-elle ? J'aurais plus vu ça dans un film de super-héros, mais jamais en informatique ...
Eh bien la notion de polymorphisme est un concept fondamental de la programmation orientée objet. Le polymorphisme permet à un objet de prendre plusieurs formes différentes. Il permet d'être utilisé de différentes manières tout en partageant des spécificités d'autres objets. La forme d'un objet change, mais pas le fond.
Je m'explique : il est possible, en programmation orientée objet, d'utiliser un objet comme étant un autre. Un chien est un animal et un chat aussi. Les deux possèdent l'action de manger. Ils mangent de la même manière l'un que l'autre. On peut donc manipuler la fonction manger de la même façon qu'on le ferait pour un chien ou un chat. Ainsi, la classe Animal a deux formes : chien et chat.
Crois-moi ou non, il est possible de manipuler un chien et un chat comme étant un même animal :
namespace Polymorphism
{
public static class Program
{
public static void Main()
{
Animal[] animalsSet = new Animal[2];
Dog dog = new Dog("Rock");
Cat cat = new Cat("Cuty");
animalsSet[0] = dog;
animalsSet[1] = cat;
foreach (Animal animal in animalsSet)
{
animal.Eat(); // Dog and Cat eat in the same manner !
}
}
}
public abstract class Animal
{
protected short _legsCount;
protected bool _hairy;
protected string _name;
public Animal(string name, short legsCount, bool hairy)
{
_name = name;
_legsCount = legsCount;
_hairy = hairy;
}
public void Eat()
{
System.Console.WriteLine(_name + " eats");
}
public string GetName()
{
return _name;
}
}
public class Dog : Animal
{
public Dog(string name) : base(name, 4, true)
{ }
public void Bark()
{
System.Console.WriteLine("Bark");
}
}
public class Cat : Animal
{
public Cat(string name) : base(name, 4, true)
{ }
public void Meow()
{
System.Console.WriteLine("Meow");
}
}
}
Précédemment, nous avons déjà décortiqué le contenu des classes Animal, Cat et Dog. Nous allons donc nous intéresser spécifiquement à ce qui se passe dans la méthode Main:
-
On définit un tableau d'animaux
-
On définit un chien
-
On définit un chat
-
On affecte à la première case du tableau d'animaux le chien
-
On affecte à la deuxième case du tableau d'animaux le chat
-
Une boucle qui fait manger le chien et le chat de la même manière
N'y a-t-il pas quelque chose d'étrange dans le code au-dessus ?
On utilise un objet de type
Animalpour faire manger chien et chat ?
Exactement ! Afin de faire manger chien et chat, nous nous servons du type Animal.
Ne m'avais-tu pas dit qu'une classe abstraite ne pouvait pas être instanciée ? Pourtant, tu utilises une instance de classe
Animal, m'aurais-tu menti ?
Il est impossible de créer une instance de classe abstraite, ceci est toujours vrai. Cependant, il est possible de manipuler une classe-fille avec son type de base. Plus clairement, on peut traiter un chien et un chat comme un animal, puisqu'ils partagent des caractéristiques communes à des animaux. Aucune instance de la classe Animal n'est créée. Les objets de type Animal que nous manipulons sont en fait des instances de Cat et Dog manipulées comme étant des animaux. Par conséquent, les classes Cat et Dog prennent une autre forme: celle d'un objet de classe Animal. Voici pourquoi on parle de polymorphisme, car un objet peut prendre une autre forme, celle de sa classe-mère.
Lire la partie "Run-Time Polymorphism"
La surcharge de méthodes
Toutes les espèces, au cours de leur évolution, ont changé. Ces espèces ont cependant conservé des caractéristiques propres à leurs ancêtres. Par exemple : les reptiles descendent des dinosaures, mais ont réduit en taille et sont devenus plus statiques. Un reptile a conservé la poïkilothermie de ses ancêtres les dinosaures, et leurs écailles, mais ne fait plus la même taille et n'a plus les mêmes réflexes.
Dans notre exemple précédent de chien et chat, on suppose qu'ils mangent de la même manière, ce qui est approximativement réel. Mais une moule ne mange pas de la même manière qu'un chien ou un chat. Nous avons donc un problème : si je veux dériver une classe Mussel de la classe Animal, je dois changer sa manière de manger alors que la méthode Eat est définie dans la classe abstraite Animal. Si je change la méthode de ma classe abstraite Animal, Cat et Dog mangeront de la même manière qu'un dauphin, ce qui est absolument faux.
Pour pallier ce problème, on peut faire une surcharge de méthode. Une surcharge de méthode est tout simplement un changement du corps de la méthode dans une classe dérivée. C'est-à-dire qu'une classe enfant peut changer le comportement imposé par son parent en surchargeant une des méthodes qu'il hérite de sa classe-mère.
Une surcharge de méthode se fait à l'aide de plusieurs modificateurs : virtual et override. Il faut placer virtual devant la signature d'une méthode de la classe parent pour spécifier que cette méthode pourra être surchargée. Il faut ensuite placer le mot-clé override devant la méthode que l'on surcharge dans la classe enfant. Un peu de code pour illustrer le processus :
namespace PolymorphismWithOverride
{
public static class Program
{
public static void Main()
{
Animal[] animalsSet = new Animal[3];
Dog dog = new Dog("Rock");
Cat cat = new Cat("Cuty");
Mussel mussel = new Mussel("Puffy");
animalsSet[0] = dog;
animalsSet[1] = cat;
animalsSet[2] = mussel;
foreach (Animal animal in animalsSet)
{
animal.Eat(); // Dog and Cat eat in the same manner !
}
}
}
public abstract class Animal
{
protected short _legsCount;
protected bool _hairy;
protected string _name;
public Animal(string name, short legsCount, bool hairy)
{
_name = name;
_legsCount = legsCount;
_hairy = hairy;
}
public virtual void Eat()
{
System.Console.WriteLine(_name + " eats");
}
public string GetName()
{
return _name;
}
}
// [The Cat & Dog classes should be here but they've been removed here for simplicity]
public class Mussel : Animal
{
public Mussel(string name) : base(name, 0, false)
{ }
public override void Eat()
{
System.Console.WriteLine(_name + " is filtering water to get something to eat");
}
}
}
Dans le code ci-dessus, la méthode Eat de la classe abstraite Animal a été surchargée dans la classe Mussel. La manière de se nourrir change uniquement pour la moule, alors la méthode Eat est uniquement surchargée dans la classe Mussel. Le reste des classes garde un comportement standard.
Grâce au mot-clé virtual, présent avant la signature de la méthode surchargeable dans la classe abstraite Animal, et au mot-clé override avant la signature de la méthode Eat actuellement surchargée dans la classe Mussel, on a fait en sorte qu'une moule ait un comportement particulier, différent du reste des animaux lorsqu'il s'agit de manger.
D'autres modificateurs à mettre avant la signature de la méthode à surcharger existent et ont des effets différents :
-
override oblige la surcharge de la méthode en question dans les classes enfants
-
abstract oblige la méthode à ne pas avoir de corps et force la surcharge
Challenge
Une classification génétique
Concevoir un programme classifiant certaines espèces :
- Une classe abstraite
Animal(mammifère)- Bipèdes :
- L'humain
- Le singe
- Quadrupèdes :
- L'éléphant
- La tortue
- Le lézard
- Bipèdes :
Chaque classe abstraite implémente une méthode Move ne prenant aucun argument et qui affiche la manière dont l'espèce se déplace. La méthode Move pourra être surchargée en fonction des espèces.
Chaque classe abstraite implémente aussi une méthode Eat, commune à tous les animaux en question, puisqu'ils ont tous une bouche avec laquelle ils avalent la nourriture.
Le design du programme est libre et le résultat doit être assez proche de la réalité biologique, sans avoir besoin d'être pointilleusement scientifique.
Libre à toi de rajouter ce que tu veux tant que les critères sont satisfaits.
Amuse toi.
Critères de validation
- Un lien vers ta solution sur GitHub
- Une classe abstraite
Animalreprésente les traits caractéristiques se retrouvant chez des animaux. Tous les animaux en héritent. - La méthode
Movede la classe abstraite est surchargée au moins une fois - Un animal a un nombre de pattes qui change en fonction de son espèce