Chapitre 2 – L’encapsulation
Document réservé
Vous consultez actuellement la présentation publique de ce chapitre.
Les documents PDF complets, comprenant les développements théoriques, les exemples détaillés et les exercices, sont disponibles sur demande.
Pour obtenir un accès, contactez-moi via la page Contact en indiquant les domaines qui vous intéressent (C++, ESP-IDF, électronique, etc.).
2.2 Les limites des membres publics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.3 Protéger la représentation interne . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.4 Faut-il toujours écrire des setters ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.5 Additionner deux vecteurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.6 Calculer la norme d’un vecteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.7 Calculer le produit scalaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.8 Obtenir un vecteur unitaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.10 Exercices du chapitre 12 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2026 – C++ Partie II
AccueilDans ce chapitre, nous allons commencer à construire une bibliothèque de calcul vectoriel 1 . Nous l’enrichirons progressivement à mesure que nous découvrirons de nouvelles possibilités du langage Dans un repère du plan, un vecteur est déterminé par deux composantes. Par exemple, La première composante est associée à l’axe horizontal et la seconde à l’axe vertical 2 . Une première classe permettant de représenter ce vecteur pourrait être écrite de la manière suivante : La classe contient deux données membres, x et y, qui représentent les deux composantes du vecteur. Elles sont placées dans la partie publique de la classe et sont donc directement accessibles depuis Nous pouvons ainsi créer un objet représentant le vecteur (3, 4) : L’objet u représente maintenant le vecteur 1. Une bibliothèque est un ensemble de fonctions, de classes, de structures et de constantes conçues pour être réutilisées dans plusieurs programmes. Ces éléments peuvent être regroupés dans un espace de noms. Les déclarations nécessaires à leur utilisation sont généralement placées dans un ou plusieurs fichiers d’en-tête, inclus à l’aide de la 2. Dans ce chapitre, nous travaillerons dans le plan vectoriel euclidien muni d’une base orthonormée. Ce choix nous permettra notamment d’exprimer simplement la norme et le produit scalaire à partir des composantes des vecteurs. 2026 – C++ Partie II Toutes les valeurs réelles peuvent être utilisées comme composantes d’un vecteur. Contrairement à une date, un vecteur ne risque donc pas de contenir un mois égal à 15 ou un jour Dans ce cas, pourquoi ne pas simplement laisser les membres x et y publics ? Les limites des membres publics Dans la première version de notre classe, les membres x et y sont intentionnellement publics. Toutes les parties du programme peuvent donc y accéder directement. Cela permet de consulter facilement les composantes d’un vecteur : std::cout << u.x << std::endl; std::cout << u.y << std::endl; Mais cela permet également de les modifier à tout moment : La valeur 12.0 est une composante parfaitement valide. Le problème n’est donc pas que cette instruction rende nécessairement le vecteur incohérent. Le problème est que la classe ne contrôle pas les modifications apportées à ses données. Une modification accidentelle est alors impossible à distinguer d’une modification volontaire. Dans la classe Date étudiée au chapitre précédent, les membres privés permettaient notamment d’empêcher l’introduction de valeurs impossibles. L’encapsulation ne sert cependant pas uniquement à valider des données. Elle permet également à une classe de contrôler la manière dont son état est consulté et modifié. Les membres publics créent une autre difficulté. Le programme qui utilise la classe dépend directement des noms et de l’organisation de ses données. Si nous décidons un jour de renommer les membres ou de représenter les vecteurs autrement, toutes les instructions qui accèdent directement à x et y devront être retrouvées et modifiées. Notre classe ne propose encore aucune opération vectorielle. Par exemple, le calcul de la norme doit être effectué directement par le programme qui utilise le vecteur :
Accueil2026 – C++ Partie II std::sqrt(u.x * u.x + u.y * u.y); Le programme doit donc connaître à la fois la représentation du vecteur et les calculs à effectuer sur ses composantes. La classe se contente pour l’instant de regrouper deux nombres. Les membres x et y constituent la représentation interne du vecteur. Les opérations que la classe mettra à la disposition du programme constitueront son interface publique. • Des membres publics peuvent être consultés et modifiés depuis toutes les parties du • Le programme devient alors directement dépendant de la représentation interne de la • L’interface publique regroupe les opérations proposées pour utiliser les objets de la classe. Nous souhaitons maintenant rendre les membres x et y privés. Comment pourrons-nous alors : • fournir les deux composantes lors de la création d’un vecteur ? • consulter ensuite ces composantes depuis la fonction main() ? Protéger la représentation interne Pour empêcher l’accès direct à la représentation interne du vecteur, nous plaçons les membres x et y dans la partie privée de la classe. Nous devons cependant conserver un moyen de fournir les composantes lors de la création du vecteur et de les consulter par la suite. Nous utiliserons pour cela un constructeur et deux getters. Code 2.1 – Vecteur2D.h Vecteur2D(double xInitial, double yInitial);
Accueil2026 – C++ Partie II double getX() const; double getY() const; Le constructeur reçoit les deux composantes et initialise les membres privés à l’aide d’une liste Code 2.2 – Vecteur2D.cpp #include « Vecteur2D.h » Vecteur2D::Vecteur2D( double Vecteur2D::getX() const double Vecteur2D::getY() const Nous pouvons maintenant créer un vecteur en fournissant ses deux composantes au constructeur : Vecteur2D u(3.0, 4.0); Dès sa création, l’objet u représente donc le vecteur (3, 4). Il n’est plus nécessaire d’affecter séparément ses deux composantes. En revanche, l’accès direct à un membre privé est maintenant refusé par le compilateur : std::cout << u.x << std::endl; // Erreur Pour consulter les composantes, il faut passer par les getters appartenant à l’interface publique de std::cout << u.getX() << std::endl; std::cout << u.getY() << std::endl;
Accueil2026 – C++ Partie II Les getters peuvent accéder directement aux membres x et y, car ils appartiennent à la classe Ils sont déclarés avec le mot-clé const, car ils consultent le vecteur sans le modifier. L’instruction suivante est-elle acceptée par le compilateur ? Justifiez votre réponse. Non. La classe ne possède pas de constructeur sans paramètre. Les deux composantes doivent donc être fournies lors de la création du vecteur. • Les membres privés constituent la représentation interne du vecteur. • Le constructeur définit les composantes lors de la création de l’objet. • Les getters permettent de consulter les composantes sans donner un accès direct aux Puisque les membres x et y sont maintenant privés, faut-il nécessairement ajouter deux setters setX() et setY() pour pouvoir les modifier ? Faut-il toujours écrire des setters ? Dans le chapitre précédent, nous avons utilisé des setters pour modifier les membres privés de la classe Date. Nous pourrions appliquer la même solution à notre classe Vecteur2D : void setX(double nouveauX); void setY(double nouveauY); Leur implémentation ne présenterait aucune difficulté particulière. void Vecteur2D::setX(double nouveauX) void Vecteur2D::setY(double nouveauY)
Accueil2026 – C++ Partie II Ces méthodes permettraient de modifier séparément les deux composantes du vecteur : Vecteur2D u(3.0, 4.0); Le vecteur u, initialement égal à (3, 4), deviendrait alors le vecteur (5, 7). Ces deux setters seraient parfaitement valides en C++ et toutes les valeurs reçues représenteraient encore des composantes Puisque les setters précédents sont valides et faciles à écrire, devons-nous nécessairement les ajouter à la classe ? Non. Une classe ne doit pas fournir automatiquement un setter pour chacun de ses membres privés. Son interface publique doit être choisie en fonction des opérations que nous souhaitons proposer. Une instruction telle que décrit une modification de la représentation interne du vecteur. Elle n’exprime pas directement une opération vectorielle telle qu’une addition ou une multiplication par un scalaire. Nous choisirons donc de ne pas ajouter les méthodes setX() et setY() à notre classe. Une fois construit, un vecteur pourra être consulté et utilisé pour produire de nouveaux vecteurs, mais son interface ne permettra pas de modifier séparément ses composantes. Les setters ne sont pas incorrects en eux-mêmes. Dans une autre bibliothèque, il pourrait être parfaitement justifié d’autoriser la modification séparée des composantes. Leur absence constitue ici un choix de conception adapté à l’interface mathématique que nous souhaitons construire. La présence d’un membre privé impose-t-elle la présence : Non dans les deux cas. Un membre privé ne doit posséder un getter ou un setter que si l’interface publique de la classe doit permettre l’opération correspondante.
Accueil2026 – C++ Partie II Dans notre classe, les getters sont utiles pour consulter les composantes cartésiennes du vecteur. Les setters ne correspondent pas à une opération que nous souhaitons proposer. Rendre un membre privé, puis fournir systématiquement un getter et un setter, ne constitue pas à lui seul une bonne encapsulation. L’encapsulation demande également de réfléchir aux opérations que la classe doit réellement • Un membre privé ne possède pas obligatoirement un getter ou un setter. • L’interface publique doit proposer des opérations utiles et cohérentes. • Les setters sont un choix de conception et non une obligation. Si nous ne modifions pas directement les composantes de u, comment pourrons-nous représenter le résultat d’une opération telle que Additionner deux vecteurs L’addition de deux vecteurs s’effectue composante par composante. Si u + v = (x u + x v , y u + y v ). Nous allons ajouter à la classe une méthode qui reçoit un autre vecteur et retourne le résultat de Vecteur2D additionner(const Vecteur2D& autre) const; Cette déclaration contient deux utilisations du mot-clé const : • const Vecteur2D& autre indique que la méthode ne peut pas modifier le vecteur reçu en • le dernier const indique que la méthode ne peut pas modifier le vecteur qui l’appelle. La méthode peut être implémentée de la manière suivante :
Accueil2026 – C++ Partie II Vecteur2D Vecteur2D::additionner(const Vecteur2D& autre) const return Vecteur2D( x + autre.x, y + autre.y); L’expression Vecteur2D(…) appelle le constructeur afin de créer le vecteur résultant. Celui-ci est ensuite retourné par la méthode. Nous pouvons maintenant additionner deux vecteurs : Vecteur2D u(3.0, 4.0); Vecteur2D v(1.0, 2.0); Vecteur2D w = u.additionner(v); Le vecteur w vaut alors Les vecteurs u et v ne sont pas modifiés. L’addition fait intervenir deux vecteurs, u et v. Pourquoi la méthode additionner() ne possède-t-elle qu’un seul paramètre explicite ? Le premier vecteur est l’objet qui appelle la méthode. Il est transmis implicitement à celle-ci. Seul le second vecteur doit apparaître dans la liste des paramètres. Dans l’implémentation de la méthode, pourquoi peut-on écrire alors que le membre x est privé ? Une méthode de la classe Vecteur2D peut accéder aux membres privés de tous les objets de cette classe, et pas uniquement à ceux de l’objet qui l’appelle. Le mot-clé private limite l’accès au niveau de la classe et non au niveau de chaque objet. Deux objets de la même classe ne possèdent pas des règles d’accès différentes.
Accueil2026 – C++ Partie II Considérons maintenant la déclaration suivante : void additionner(const Vecteur2D& autre); Cette méthode aurait-elle le même comportement que la précédente ? Non. Comme elle ne retourne aucun vecteur, cette méthode devrait modifier l’objet qui l’appelle. Elle ne pourrait donc pas être déclarée const. Avec cette seconde conception, l’instruction modifierait directement u. Dans notre bibliothèque, nous avons choisi de conserver les deux vecteurs additionnés et de retourner un troisième vecteur contenant le résultat. Les objets d’une même classe n’ont pas de membres privés les uns par rapport aux autres. Une méthode de la classe peut donc accéder aux membres privés de toutes ses instances, à condition de disposer d’une référence ou d’un pointeur vers celles-ci. Ce droit d’accès n’implique cependant pas toujours un droit de modification. Dans notre méthode, le paramètre autre est déclaré const. La méthode peut donc lire autre.x et autre.y, mais elle ne peut pas modifier leurs valeurs. De même, le const placé à la fin de la déclaration interdit de modifier l’objet qui appelle la méthode. Cette distinction réapparaîtra lorsque nous étudierons la surcharge des opérateurs. Une addition écrite avec l’opérateur + retourne généralement un nouvel objet, tandis que l’opérateur += modifie son opérande de gauche. • Une méthode membre reçoit implicitement l’objet qui l’appelle. • Une méthode déclarée const ne modifie pas cet objet. • Une opération peut construire et retourner un nouvel objet sans modifier ses opérandes. La multiplication d’un vecteur par un scalaire doit-elle également modifier le vecteur existant, ou peut-elle retourner un nouveau vecteur ?
Accueil2026 – C++ Partie II Calculer la norme d’un vecteur Avant de poursuivre, le lecteur est invité à compléter lui-même la classe Vecteur2D en lui ajoutant une méthode permettant de multiplier un vecteur par un scalaire. Cette méthode devra retourner un nouveau vecteur sans modifier celui qui l’appelle. Il s’agit d’un nombre réel positif ou nul. La méthode chargée de la calculer retournera donc un double norme() const; Cette méthode ne reçoit aucun paramètre, car elle calcule la norme du vecteur qui l’appelle. Elle est déclarée const, puisqu’elle ne modifie pas ce vecteur. La fonction std::sqrt() permet de calculer une racine carrée. Elle est déclarée dans le fichier d’en-tête <cmath>. #include « Vecteur2D.h » double Vecteur2D::norme() const return std::sqrt(x * x + y * y); Nous pouvons maintenant calculer la norme du vecteur (3, 4) : Vecteur2D u(3.0, 4.0); std::cout << u.norme() << std::endl; Le programme affiche : Pourquoi la méthode norme() retourne-t-elle un double plutôt qu’un objet de type
Accueil2026 – C++ Partie II La norme d’un vecteur est un scalaire et non un vecteur. Quelle valeur la méthode norme() retourne-t-elle pour le vecteur nul (0, 0) ? Elle retourne 0, car • Une méthode peut retourner un type différent de celui de sa classe. • Une méthode qui utilise uniquement l’objet qui l’appelle ne nécessite aucun paramètre • Une méthode qui ne modifie pas cet objet doit être déclarée const. Comment une méthode pourrait-elle utiliser deux vecteurs sans les modifier et retourner un Calculer le produit scalaire Dans une base orthonormée, le produit scalaire des vecteurs u · v = xu x v + y u y v . Le résultat est un scalaire. La méthode retournera donc un double : double produitScalaire( const Vecteur2D& autre) const; Comme pour l’addition, le premier vecteur est l’objet qui appelle la méthode et le second est reçu en paramètre. Aucun des deux vecteurs ne doit être modifié. La méthode s’implémente directement à partir des composantes des deux vecteurs : double Vecteur2D::produitScalaire( const Vecteur2D& autre) const return x * autre.x + y * autre.y; Considérons les deux vecteurs
Accueil2026 – C++ Partie II Le produit scalaire peut être calculé de la manière suivante : Vecteur2D u(3.0, 4.0); Vecteur2D v(1.0, 2.0); std::cout << resultat << std::endl; double resultat = u.produitScalaire(v); Le programme affiche : u · v = 3 · 1 + 4 · 2 = 11. Pourquoi la méthode additionner() retourne-t-elle un Vecteur2D, tandis que la méthode produitScalaire() retourne un double ? L’addition de deux vecteurs produit un nouveau vecteur, tandis que leur produit scalaire produit un Quel est le produit scalaire des vecteurs Que peut-on en déduire ? Le produit scalaire vaut 3 · (−4) + 4 · 3 = 0. Les deux vecteurs sont donc orthogonaux. La méthode produitScalaire() peut accéder directement aux membres privés de l’objet reçu en paramètre, car les deux objets appartiennent à la classe Vecteur2D. Le paramètre et l’objet qui appelle la méthode sont tous deux protégés contre les modifications par les deux utilisations du mot-clé const.
Accueil2026 – C++ Partie II • Une méthode peut faire intervenir plusieurs objets de la même classe. • Le type retourné dépend de la nature mathématique du résultat. • Le produit scalaire de deux vecteurs orthogonaux est nul. La norme et la direction d’un vecteur peuvent être calculées à partir de ses composantes. Faut-il malgré tout les enregistrer comme de nouveaux membres de la classe ? Obtenir un vecteur unitaire La norme et la direction d’un vecteur peuvent être calculées à partir de ses composantes. Il n’est donc pas nécessaire de les enregistrer comme de nouveaux membres de la classe. Le vecteur unitaire associé à un vecteur non nul u est défini par Il possède la même direction et le même sens que u, mais sa norme est égale à 1. Nous ajoutons à la classe une méthode retournant ce nouveau vecteur : Vecteur2D vecteurUnitaire() const; La méthode commence par calculer la norme du vecteur. Elle utilise ensuite cette valeur pour diviser chacune de ses composantes. Vecteur2D Vecteur2D::vecteurUnitaire() const Cette méthode réutilise la méthode norme() déjà fournie par la classe. Il n’est donc pas nécessaire de réécrire le calcul de la norme. Une méthode peut appeler une autre méthode de la même classe. Comme norme() et vecteurUnitaire() sont toutes deux déclarées const, cet appel ne modifie pas le vecteur. Considérons à nouveau le vecteur Son vecteur unitaire est
Accueil2026 – C++ Partie II Nous pouvons le calculer de la manière suivante : Vecteur2D u(3.0, 4.0); double longueur = u.norme(); Vecteur2D direction = u.vecteurUnitaire(); Les deux informations sont maintenant disponibles sans avoir été enregistrées comme membres supplémentaires : • longueur vaut 5.0 ; • direction représente le vecteur (0.6, 0.8). Pourquoi serait-il inutile d’ajouter un membre norme à la classe Vecteur2D ? La norme peut être calculée à partir des membres x et y. Son enregistrement créerait une information redondante qu’il faudrait maintenir en permanence en accord avec les composantes. La méthode vecteurUnitaire() ne peut être utilisée que si le vecteur n’est pas nul. En effet, le vecteur nul possède une norme égale à zéro et n’a pas de direction. Le calcul de son vecteur unitaire provoquerait donc une division par zéro. Peut-on associer un vecteur unitaire au vecteur nul (0, 0) ? Non. Le vecteur nul ne possède aucune direction et sa norme est égale à zéro. L’expression n’est donc pas définie. La condition exigeant que le vecteur soit non nul constitue une précondition de la méthode. Une précondition est une condition qui doit être satisfaite avant l’appel d’une fonction ou d’une méthode.
Accueil2026 – C++ Partie II La précondition fait partie de l’interface de la méthode, même si elle n’apparaît pas dans sa signature. Le programme qui utilise la classe doit donc s’assurer que le vecteur n’est pas nul avant d’appeler vecteurUnitaire(). Nous verrons plus loin différents mécanismes permettant à une méthode de signaler automatiquement qu’une opération demandée ne peut pas être effectuée. • Une information facilement calculable ne doit pas nécessairement être enregistrée comme • Une méthode peut réutiliser les autres méthodes de sa classe. • Une précondition précise ce qui doit être vrai avant l’appel d’une méthode. • Le vecteur nul ne possède pas de vecteur unitaire. Dans ce chapitre, nous avons construit progressivement une classe représentant les vecteurs libres du plan. Cette classe regroupe les composantes du vecteur et les opérations mathématiques permettant La première version de la classe possédait deux membres publics : Cette représentation était simple, mais toutes les parties du programme pouvaient consulter et modifier directement les composantes. Le programme devenait également dépendant des noms et de l’organisation des données internes de la classe. Nous avons donc placé les composantes dans la partie privée et construit une interface publique composée d’un constructeur, de getters et d’opérations vectorielles. Vecteur2D(double xInitial, double yInitial); double getX() const; double getY() const; Vecteur2D additionner( const Vecteur2D& autre) const;
Accueil2026 – C++ Partie II double norme() const; double produitScalaire( const Vecteur2D& autre) const; Vecteur2D vecteurUnitaire() const; L’encapsulation consiste à regrouper dans une classe l’état d’un objet et les opérations qui le manipulent, tout en contrôlant l’accès à sa représentation interne. • les membres x et y constituent la représentation interne du vecteur ; • la partie privée empêche leur accès direct depuis l’extérieur ; • la partie publique constitue l’interface proposée au programme ; • le constructeur définit les composantes lors de la création de l’objet ; • les getters permettent de consulter les composantes ; • les autres méthodes expriment des opérations mathématiques sur les vecteurs. Une interface publique ne doit pas être obtenue en ajoutant automatiquement un getter et un setter à chaque membre privé. Elle doit être conçue en fonction des opérations que la classe doit réellement proposer. C’est pourquoi notre classe possède des getters, mais aucun setter permettant de modifier séparément ses composantes. Nous avons également choisi de ne pas enregistrer la norme ou la direction comme de nouveaux membres. Ces informations peuvent être calculées à partir des composantes. Leur stockage créerait des données redondantes qu’il faudrait maintenir en permanence dans un état cohérent. L’encapsulation ne sert pas uniquement à empêcher l’introduction de valeurs invalides. Toutes les paires de réels représentent des composantes possibles pour un vecteur du plan. Elle permet surtout de séparer la manière dont un objet est représenté des opérations proposées Les méthodes qui ne modifient pas l’objet sont déclarées avec le mot-clé const. Lorsqu’un autre vecteur est reçu par référence constante, celui-ci peut être consulté sans être modifié. double produitScalaire( const Vecteur2D& autre) const; Dans cette déclaration, le premier const protège l’objet reçu en paramètre, tandis que le second protège l’objet qui appelle la méthode. Une méthode de la classe peut accéder aux membres privés de toutes les instances de cette classe. Ce droit d’accès ne supprime cependant pas les restrictions imposées par le mot-clé const. Certaines méthodes, comme additionner() et vecteurUnitaire(), construisent et retournent un nouvel objet sans modifier celui qui les appelle. D’autres, comme norme() et produitScalaire(),
Accueil2026 – C++ Partie II retournent un scalaire. Le type retourné dépend donc de la nature mathématique du résultat. Enfin, l’interface d’une méthode ne se limite pas à sa signature. Elle comprend également son comportement et ses conditions d’utilisation. La méthode vecteurUnitaire() possède ainsi une précondition : le vecteur qui l’appelle doit être non nul. Le vecteur nul ne possède aucune direction. La méthode vecteurUnitaire() ne doit donc pas être appelée sur celui-ci. Le traitement automatique de cette situation sera étudié dans un chapitre consacré à la gestion des erreurs. Grâce à l’encapsulation, la représentation interne de la classe pourra évoluer sans nécessairement modifier les programmes qui l’utilisent. Par exemple, une future implémentation pourrait représenter un vecteur à l’aide de sa norme et de son angle. Tant que l’interface publique reste compatible, les getters et les méthodes peuvent effectuer les conversions nécessaires. • L’encapsulation sépare la représentation interne d’un objet de son interface publique. • Les membres privés ne sont accessibles qu’aux méthodes et aux autres membres autorisés • Une interface doit proposer des opérations utiles plutôt qu’un accès systématique à toutes les données. • Les informations facilement calculables ne doivent pas nécessairement être enregistrées • Une méthode déclarée const ne modifie pas l’objet qui l’appelle. • Une méthode peut construire et retourner un nouvel objet sans modifier ses opérandes. • Les préconditions font partie des règles d’utilisation d’une méthode.
Accueil2026 – C++ Partie II Exercices du chapitre 12 Ces exercices sont basés sur la classe Vecteur2D qui vous pouvez télécharger ici
AccueilP2-Chap-02- L’encapsulation
AccueilTermes à ajouter au glossaire
Codes et programmes contenus dans le chapitre
Vecteur2D
Encapsulation de la classe Vecteur2D
README.pdf
Explications sur l’utilisation des codes