Chapitre 221 minIntermédiaire
Classe et objet : la POO expliquée
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- expliquer ce qu'apporte la programmation orientée objet ;
- distinguer une classe d'un objet ;
- écrire une classe avec ses champs et ses méthodes, et l'instancier ;
- prévoir ce que fait la copie d'une référence ;
- trouver les classes d'une application à partir d'un énoncé.
4 exercices corrigés2 schémas
La programmation orientée objet (POO) est une façon d'organiser un programme en objets qui regroupent des données et les traitements qui s'y appliquent. Une classe est le plan qui décrit ces objets ; un objet est un exemplaire concret, fabriqué à partir de ce plan. Ce chapitre explique pourquoi on programme ainsi, puis comment écrire une classe en C#, créer des objets et comprendre ce qu'est une référence.
Pourquoi la programmation orientée objet
Imagine le suivi des tickets d'assistance de Sorbier Logistique écrit sans objets. Pour trois tickets, tu déclares trois séries de variables :
string titre1 = "Écran noir"; int duree1 = 45; string statut1 = "en_cours";
string titre2 = "Imprimante bloquée"; int duree2 = 20; string statut2 = "resolu";
string titre3 = "Odoo lent"; int duree3 = 60; string statut3 = "en_cours";
Avec 200 tickets, c'est intenable. Des tableaux parallèles (titres[i], durees[i], statuts[i]) font un peu mieux, jusqu'au jour où un tri déplace les titres sans déplacer les durées. Et rien n'empêche une fonction quelconque d'écrire statut2 = "n'importe quoi" : les règles de gestion sont dispersées dans tout le programme.
La POO part d'une idée simple : ce qui va ensemble reste ensemble. Un ticket a un titre, une durée, un statut (ses données) et sait faire certaines choses : passer en cours, cumuler du temps, dire s'il est urgent (son comportement). On écrit une fois la description d'un ticket, et le programme manipule ensuite des objets tickets, chacun avec ses propres valeurs.
Un objet a trois caractéristiques :
| Caractéristique | Sens | Pour un ticket |
|---|---|---|
| État | Les valeurs de ses données à un instant donné | titre « Écran noir », 45 min, en cours |
| Comportement | Ce qu'il sait faire, ses méthodes | prendre en charge, ajouter du temps, se résoudre |
| Identité | Ce qui le distingue des autres, même à état égal | c'est ce ticket-là, pas un autre qui lui ressemble |
Classe et objet : le plan et les exemplaires
Une classe décrit un type d'objets : quelles données ils portent, quelles méthodes ils offrent. Elle ne contient aucune valeur particulière. Un objet, ou instance, est un exemplaire créé à partir de la classe, avec ses propres valeurs. Créer un objet s'appelle instancier la classe.
L'image classique est celle du plan d'architecte : le plan (la classe) décrit une maison ; on construit autant de maisons (les objets) qu'on veut, chacune avec sa couleur de volets. Repeindre une maison ne change ni le plan ni les autres maisons.
| La classe | L'objet | |
|---|---|---|
| C'est… | un plan, un type | un exemplaire concret |
| Combien ? | une seule, écrite une fois | autant que nécessaire |
| Contient des valeurs ? | non, la liste des données et des méthodes | oui, les siennes |
| En C# | class Ticket { … } | new Ticket() |
| En UML | un rectangle à trois compartiments | un rectangle avec le nom souligné t1 : Ticket |
Écrire une classe en C#
Dans le projet GestParc.Console, crée un fichier Ticket.cs (dans Visual Studio : clic droit sur le projet, Ajouter → Classe). Une classe par fichier, et le fichier porte le nom de la classe : c'est la convention de tous les projets C#.
namespace GestParc.Console;
class Ticket
{
// Champs : l'état de chaque ticket
public int Numero;
public string Titre = "";
public string Priorite = "normale";
public string Statut = "ouvert";
public int DureeTotaleMin;
// Méthodes : le comportement
public void Prendre()
{
Statut = "en_cours";
}
public void AjouterTemps(int minutes)
{
DureeTotaleMin += minutes;
}
public bool EstUrgent()
{
return Priorite == "haute" || Priorite == "critique";
}
public void Afficher()
{
Console.WriteLine($"#{Numero} {Titre} [{Priorite}, {Statut}] {DureeTotaleMin} min");
}
}
Lis-la ligne par ligne :
namespace GestParc.Console;range la classe dans un espace de noms, l'équivalent d'un dossier logique. Deux classesTicketpeuvent coexister dans deux espaces de noms différents.class Ticketdéclare la classe. Son nom est un nom commun au singulier, enPascalCase.- Les champs (
Numero,Titre…) sont les variables de l'objet : chaque ticket aura les siens. Un champ peut avoir une valeur initiale (= "normale"). - Les méthodes (
Prendre,AjouterTemps…) agissent sur les champs de l'objet.Prendre()ne reçoit aucun paramètre : elle sait sur quel ticket elle travaille. publicrend le membre accessible depuis l'extérieur de la classe. Ces champs publics sont une facilité de départ : le chapitre 3 les remplacera par des propriétés protégées.
Les champs et les méthodes sont les membres de la classe. Les sujets d'examen parlent souvent d'attributs pour les champs : c'est le vocabulaire d'UML. En C#, le mot « attribut » désigne aussi autre chose (les annotations entre crochets, vues au chapitre 14) ; ce cours dit « champ » pour éviter la confusion.
Créer des objets avec new
L'opérateur new fabrique un objet et renvoie de quoi l'atteindre. On accède ensuite à ses membres avec un point.
Ticket t1 = new Ticket();
t1.Numero = 1;
t1.Titre = "Écran noir au démarrage";
t1.Priorite = "haute";
Ticket t2 = new Ticket();
t2.Numero = 2;
t2.Titre = "Bourrage papier, imprimante du 2e étage";
t1.Afficher();
t2.Afficher();
t1.Prendre();
t1.AjouterTemps(30);
t1.AjouterTemps(45);
t1.Afficher();
Console.WriteLine(t1.EstUrgent());
Console.WriteLine(t2.EstUrgent());
#1 Écran noir au démarrage [haute, ouvert] 0 min
#2 Bourrage papier, imprimante du 2e étage [normale, ouvert] 0 min
#1 Écran noir au démarrage [haute, en_cours] 75 min
True
False
t1.Prendre() a modifié le statut de t1 seulement : t2 est resté ouvert. C'est le cœur de la POO, chaque objet a son propre état. La notation t1.AjouterTemps(30) se lit « demande à t1 d'ajouter 30 minutes » : en POO, on dit qu'on envoie un message à l'objet.
Écrire new Ticket() puis remplir les champs un par un est fastidieux et dangereux : on peut oublier le titre. Le chapitre 4 règle ce problème avec les constructeurs. Depuis C# 9, tu peux aussi écrire Ticket t = new(); quand le type est déjà écrit à gauche.
Le mot-clé this
Dans une méthode, this désigne l'objet sur lequel la méthode est appelée. Quand tu écris Statut = "en_cours"; dans Prendre(), C# comprend this.Statut. On écrit this explicitement dans deux cas. Le premier exemple suppose un champ privé titre, écrit en camelCase (le chapitre 3 explique pourquoi les champs sont privés) :
private string titre = "";
public void Renommer(string titre)
{
this.titre = titre; // this.titre : le champ ; titre : le paramètre qui porte le même nom
}
public void AffecterA(Technicien technicien)
{
technicien.Prendre(this); // l'objet se passe lui-même en paramètre
}
Le premier cas est fréquent dans les constructeurs ; nommer le paramètre autrement (nouveauTitre) évite d'en avoir besoin. Le second sert à relier deux objets, comme au chapitre 5.
Un objet est une référence
Une variable de type classe ne contient pas l'objet : elle contient une référence vers lui, c'est-à-dire l'adresse où il se trouve en mémoire (le tas, heap). Copier la variable copie l'adresse, pas l'objet.
Ticket t3 = t1; // t3 et t1 désignent le MÊME objet
t3.Titre = "Écran noir (câble HDMI)";
Console.WriteLine(t1.Titre);
Console.WriteLine(t1 == t3); // même référence ?
Ticket t4 = new Ticket(); // un autre objet, même contenu
t4.Numero = 1;
t4.Titre = "Écran noir (câble HDMI)";
Console.WriteLine(t1 == t4);
Écran noir (câble HDMI)
True
False
Les types simples (int, decimal, bool, DateOnly…) sont des types valeur : la variable contient directement la valeur, et la copier crée une copie indépendante.
int a = 10;
int b = a;
b = 20;
Console.WriteLine($"a = {a}, b = {b}"); // a = 10, b = 20
| Type valeur | Type référence | |
|---|---|---|
| Exemples | int, double, decimal, bool, char, DateOnly, DateTime, les enum et les struct | string, les tableaux, les listes, toutes tes classes |
| La variable contient | la valeur | l'adresse de l'objet |
b = a | copie la valeur | copie l'adresse : deux noms pour un objet |
== compare | les valeurs | les adresses (sauf si la classe redéfinit l'égalité, comme string) |
Peut valoir null | non, sauf int? | oui, si le type est déclaré nullable (Ticket?) |
Une variable référence peut ne désigner aucun objet : elle vaut null. Appeler une méthode dessus lève une NullReferenceException, l'erreur d'exécution la plus fréquente en POO.
Ticket? t5 = null;
Console.WriteLine(t5?.Titre ?? "aucun ticket"); // aucun ticket
// t5.Afficher(); // NullReferenceException à l'exécution
Valeurs par défaut des champs
Un champ que personne n'a initialisé prend la valeur par défaut de son type : 0 pour les nombres, false pour un bool, null pour une référence, le 1er janvier de l'an 1 pour une date.
Ticket vide = new Ticket();
Console.WriteLine($"[{vide.Numero}] [{vide.Titre}] [{vide.DureeTotaleMin}] [{vide.Statut}]");
// [0] [] [0] [ouvert]
Titre affiche une chaîne vide parce que la classe l'initialise à "" ; sans cette initialisation, le champ vaudrait null et le compilateur avertirait (CS8618) qu'un string non nullable risque de rester nul. Un ticket numéro 0 sans titre n'a pourtant aucun sens : c'est le constructeur qui l'empêchera.
Organiser le projet : fichiers et espaces de noms
Dès que tu as plusieurs classes, range-les :
GestParc.Console/
├── GestParc.Console.csproj
├── Program.cs le point d'entrée : crée les objets et les fait travailler
├── Ticket.cs namespace GestParc.Console;
├── Logiciel.cs
└── Poste.cs
- Un fichier par classe, au nom de la classe.
- Chaque fichier commence par
namespace NomDuProjet;(la forme sans accolades, valable pour tout le fichier). - Pour utiliser une classe d'un autre espace de noms, ajoute
using GestParc.Domaine;en tête du fichier. - Quand le projet grossit, sépare la logique métier dans une bibliothèque de classes (
dotnet new classlib -n GestParc.Domaine) que l'application console, puis l'application WinForms, référencent. Le chapitre 15 montre cette organisation complète.
Les quatre piliers de la POO
Tout le reste du cours développe quatre principes, que les sujets et les jurys appellent les piliers de la programmation objet.
| Pilier | Idée | Chapitre |
|---|---|---|
| Encapsulation | Un objet protège ses données et n'expose que des accès contrôlés | 3 |
| Héritage | Une classe spécialise une autre classe et réutilise ce qu'elle a | 7 |
| Polymorphisme | Un même appel produit un comportement adapté à l'objet réel | 8 |
| Abstraction | On manipule des concepts (une classe abstraite, une interface) sans dépendre des détails | 9 |
Trouver les classes dans un énoncé
Avant d'écrire du code, il faut décider quelles classes créer. Une méthode simple, à appliquer sur un texte qui décrit le besoin :
- Souligne les noms communs : ce sont des candidats classes ou champs. « Un technicien réalise des interventions sur un ticket ; chaque intervention a une durée et un compte rendu. »
- Garde comme classe ce qui a plusieurs données et une vie propre (technicien, ticket, intervention) ; garde comme champ ce qui n'est qu'une valeur (durée, compte rendu).
- Repère les verbes : ce sont des candidats méthodes, rangés dans la classe qui possède les données nécessaires. « Clôturer un ticket » devient
Ticket.Resoudre(). - Repère les liens entre classes (« un ticket a plusieurs interventions ») : ce sont les associations du chapitre 5.
Le résultat se dessine sous forme de diagramme de classes UML, que le cours UML détaille (diagramme de classes).
Exercices
Exercice 1 sur 4 Classe ou objet ? Réussi À revoir
Pour chaque élément, dis s'il s'agit d'une classe, d'un objet, d'un champ ou d'une méthode, dans une application de gestion de parc.
Logiciel- le logiciel « VLC » installé chez Sorbier Logistique
NbLicencesInstaller()- le poste PC-0007 de Julie Lefèvre
Voir la réponseMasquer la réponse de l'exercice 1
- Une classe : elle décrit tous les logiciels.
- Un objet, une instance de
Logiciel, avec ses propres valeurs. - Un champ : une donnée que chaque logiciel possède.
- Une méthode : un comportement du logiciel (ou du poste, selon la conception).
- Un objet, une instance de
Poste.
Tu avais trouvé ?
Exercice 2 sur 4 Écrire la classe Logiciel Réussi À revoir
Écris une classe Logiciel avec trois champs publics, Nom (texte), NbLicences et NbInstallations (entiers), et trois méthodes :
LicencesDisponibles(), qui renvoie le nombre de licences encore libres ;PeutInstaller(), qui renvoie vrai s'il reste au moins une licence ;Installer(), qui ajoute une installation.
Puis crée Microsoft 365 avec 10 licences, installe-le deux fois et affiche les licences disponibles.
Voir la réponseMasquer la réponse de l'exercice 2
class Logiciel
{
public string Nom = "";
public int NbLicences;
public int NbInstallations;
public int LicencesDisponibles()
{
return NbLicences - NbInstallations;
}
public bool PeutInstaller()
{
return LicencesDisponibles() > 0;
}
public void Installer()
{
NbInstallations++;
}
}
Logiciel office = new Logiciel();
office.Nom = "Microsoft 365";
office.NbLicences = 10;
office.Installer();
office.Installer();
Console.WriteLine(office.LicencesDisponibles()); // 8
PeutInstaller() appelle LicencesDisponibles() au lieu de refaire le calcul : une règle écrite une seule fois. Remarque le défaut de cette classe : Installer() ne vérifie rien, et n'importe qui peut écrire office.NbInstallations = -5;. Le chapitre suivant corrige ces deux problèmes.
Tu avais trouvé ?
Exercice 3 sur 4 Suivre les références Réussi À revoir
Avec la classe Ticket du chapitre, donne les quatre lignes affichées.
Ticket a = new Ticket();
a.Titre = "Souris HS";
Ticket b = a;
Ticket c = new Ticket();
c.Titre = "Souris HS";
b.AjouterTemps(10);
c.AjouterTemps(5);
a.AjouterTemps(20);
Console.WriteLine($"{a.DureeTotaleMin} {b.DureeTotaleMin} {c.DureeTotaleMin}");
Console.WriteLine(a == b);
Console.WriteLine(a == c);
b = c;
b.Titre = "Clavier HS";
Console.WriteLine($"{a.Titre} / {c.Titre}");
Voir la réponseMasquer la réponse de l'exercice 3
30 30 5
True
False
Souris HS / Clavier HS
Il n'existe que deux objets. a et b désignent le premier : il reçoit 10 puis 20 minutes, soit 30, et on le lit par les deux noms. c désigne le second, qui a reçu 5 minutes. a == b compare deux références identiques ; a == c compare deux objets distincts, même si leurs titres étaient égaux. Après b = c, b désigne le second objet : changer b.Titre change donc c.Titre, et a n'est pas touché.
Tu avais trouvé ?
Exercice 4 sur 4 Trouver les classes Réussi À revoir
Voici l'expression d'un besoin : « Le service informatique prête des ordinateurs portables aux salariés en déplacement. Chaque prêt concerne un portable et un salarié, avec une date de début et une date de retour prévue. Un portable a un numéro d'inventaire, une marque et un modèle. Le technicien enregistre le retour du portable et note son état. »
Propose les classes, leurs champs et au moins deux méthodes.
Voir la réponseMasquer la réponse de l'exercice 4
Une proposition :
| Classe | Champs | Méthodes |
|---|---|---|
Portable | numéro d'inventaire, marque, modèle | EstDisponible() |
Salarie | nom, prénom, service | |
Pret | portable, salarié, date de début, date de retour prévue, date de retour réelle, état au retour | EnregistrerRetour(date, etat), EstEnRetard(aujourdhui) |
Le prêt est une classe à part entière : il a ses propres données (les dates, l'état) et relie un portable à un salarié. Le technicien n'a pas besoin d'une classe tant que l'application ne mémorise pas qui a enregistré le retour. « Enregistrer le retour » devient une méthode de Pret, qui possède les dates. EstDisponible() doit savoir si un prêt est en cours : il faudra relier le portable à ses prêts, ce que permet une association (chapitre 5).
Tu avais trouvé ?