POO en TS 2 - Encapsulation
La notion d'encapsulation est fondamentale dans la POO.
Pour rappel, un objet permet de regrouper des données (attributs), et le code accédant à ces données (méthodes), au sein d'une même structure. C'est justement ce qu'on appelle l'encapsulation. Mais en plus de regrouper les données et le code, l'encapsulation permet de cacher au code qui utilise un objet les détails de son implémentation.
Ainsi, le code qui utilise un objet n'est pas censé modifier les attributs de cet objet directement. Pour agir sur l'objet, il va appeler des méthodes sur celui-ci, et ce sont ces méthodes qui vont agir sur les attributs.
L'objet est donc vu par le code qui l'utilise comme une sorte de "boîte noire".
Objectifs
- L'objet vu comme une "boîte noire"
- Visibilité : attributs publics vs privés
L'objet vu comme une "boîte noire"
Dans la programmation orientée objet, les objets sont vus par le code qui les utilise (appelé "code client") comme des boîtes noires. Voici un exemple, avec une classe Car.
class Car {
private numGears: number;
private maxSpeed: number;
private currentGear: number;
private currentSpeed: number;
private isPowerOn: boolean;
private isHandbrakeOn: boolean;
constructor(numGears: number, maxSpeed: number) {
this.numGears = numGears;
this.maxSpeed = maxSpeed;
this.currentGear = 0;
this.currentSpeed = 0;
this.isPowerOn = false;
this.isHandbrakeOn = true;
}
public turnOn(): void {
this.isPowerOn = true;
}
public turnOff(): void {
this.isPowerOn = false;
}
public lowerHandbrake(): void {
this.isHandbrakeOn = false;
}
public raiseHandbrake(): void {
this.isHandbrakeOn = true;
}
public changeGear(gear): void {
this.currentGear = gear;
}
public accelerate(): void {
if (this.currentSpeed < this.maxSpeed) {
this.currentSpeed += 5;
}
}
public decelerate(): void {
if (this.currentSpeed > 0) {
this.currentSpeed -= 5;
}
}
}
const myCar = new Car(5, 180);
// Mettre le contact
myCar.turnOn();
// Baisser le frein à main
myCar.lowerHandbrake();
// Passer la 1ère
myCar.changeGear(1);
// Accélérer
myCar.accelerate();
// etc.
// ...
myCar.decelerate();
myCar.changeGear(0);
myCar.raiseHandbrake();
myCar.turnOff();
le code client, qui utilise une instance de Car, ne modifie pas directement les attributs de cette dernière. L'objet offre au code qui l'utilise une "interface" - un ensemble de méthodes - pour agir sur les valeurs de ses attributs.
Du point de vue du code qui utilise une instance de Car, celle-ci est vue ainsi :

Ainsi, si on doit changer l'implémentation de Car - le code des méthodes, les noms des attributs, etc. - ce sera transparent pour le code qui utilise les instances de Car.
Visibilité des attributs et des méthodes
La plupart des langages objets permettent de contrôler la "visibilité" des attributs et méthodes des instances d'une classe, c'est-à-dire la possibilité ou non, pour le code client de ces instances, d'accéder directement à ces attributs et méthodes.
On distingue notamment les attributs et méthodes accessibles directement par le code client (dits "publics") ou non (dits "privés"). La plupart des langages disposent également d'attributs "protégés", ce qui est lié à la notion d'héritage, dont nous parlerons plus tard.
Empêcher le code client de modifier directement des attributs d'une instance, en les indiquant comme "privés", sert l'objectif d'encapsulation du code. De cette façon, on oblige les opérations sur l'objet à être effectuées via "l'interface" fournie par les différentes méthodes. On peut ainsi éviter, entre autres, des modifications accidentelles d'attributs de l'objet par du code extérieur, lesquelles peuvent être sources de bugs.
class PublicPrivateExemple {
public isVisibleFromOutside: boolean;
private isVisibleFromOutside2: boolean;
constructor() {
this.isVisibleFromOutside = true;
this.isVisibleFromOutside2 = true;
}
}
const testPubPriv = new PublicPrivateExemple();
console.log(testPubPriv.isVisibleFromOutside);
// affichera true
console.log(testPubPriv.isVisibleFromOutside2);
// Property 'isVisibleFromOutside2' is private and only accessible within class 'PublicPrivateExemple'.
Cet exemple nous montre que les attributs public sont accessibles en dehors de la classe, alors que ceux privés ne sont pas accessible en dehors de celle-ci.
Les mots clé private/public fonctionnent pour les attributs, mais il est tout aussi vrai pour les méthodes de la classe
class PublicPrivateExemple {
public isVisible(): boolean {
return true;
}
private isVisible2(): boolean {
return false;
}
}
const testPubPriv = new PublicPrivateExemple();
console.log(testPubPriv.isVisible()); // true
// affiche true
console.log(testPubPriv.isVisible2()); // true
//Property 'isVisible2' is private and only accessible within class 'PublicPrivateExemple'.ts(2341)
L'utilisation de ces mots clés te permettront de définir avec finesse ce que tu souhaites "exposer" aux yeux des autres développeur et ce qui est interne à la classe et qui ne devrait pas être modifié.
Il sont les piliers de l'encapsulation, mais on aussi l'avantage de décrire rapidement le role de chaque fonction (utilisation externe ou interne uniquement).
Le point positif ?
Cette notion de visibilité des attributs étant très répandue parmi les langages objets, il est important d'y être sensibilisé, tu es presque sûr de la rencontrer à nouveau dans d'autre langage.
N'hésite pas à creuser les ressources si ce sujet t'intéresse.
Challenge
Ecrire une classe avec un attribut privé
En t'inspirant de l'exemple ci-dessus, écris une classe BankCustomer.
- Son constructeur devra prendre deux paramètres, le premier étant le nom du client, le second, le code secret de sa carte bleue.
- Ces paramètres devront être stockés de façon à n'être pas visibles depuis le code appelant (privés, donc).
- Une méthode
getNamepermettra de récupérer le nom du client. - Une méthode
verifyPinInputdevra permettre de vérifier si un code secret entré correspond bien au code stocké par l'instance, et renvoyertruesi c'est le cas,falsesinon.
On te fournit un squelette de programme, à compléter et à tester avec Node.js.
N'oublie pas d'installer le ou les modules nécessaires avant
import assert from 'assert';
// ... Ton code ici ...
// Tests
const customer = new BankCustomer('John Doe', '3579');
assert.equal(typeof customer.getName, 'function');
assert.equal(typeof customer.verifyPinInput, 'function');
assert.equal(customer.getName(), 'John Doe');
assert.ok(customer.verifyPinInput('3579'));
Critères de validation
- Poste ton code sur un Gist. Il doit passer les tests.
Ressources partagées sur cette quête
- Utile pour installer "assert" avec TS en cas de message d'erreur en lien avec "assert"https://stackoverflow.com/a/49658434