Cours JavaScript et TypeScript · Module 7: TypeScript avancé

Les modificateurs d'accès (public, private, protected)

16 min de lecture
Dans cette leçon9

« La sécurité à Jurassic Park est une question de la plus haute importance », déclare fermement John Hammond, président d'InGen. « Certaines données et procédures doivent être accessibles à tous les employés, d'autres uniquement à ceux disposant des habilitations appropriées, et d'autres encore sont si critiques que seule la direction y a accès. »

Tout comme à Jurassic Park, en programmation orientée objet, contrôler l'accès aux propriétés et aux méthodes d'une classe est un aspect fondamental d'une bonne conception. TypeScript propose trois modificateurs d'accès qui te permettent de définir précisément qui peut utiliser tel ou tel élément d'une classe, et dans quelle mesure : public, private et protected.

Introduction aux modificateurs d'accès

Les modificateurs d'accès déterminent depuis quels niveaux du code tu peux accéder aux propriétés et aux méthodes d'une classe :

  1. public - accessible depuis n'importe où dans le code (valeur par défaut)
  2. private - accessible uniquement à l'intérieur de la classe elle-même
  3. protected - accessible à l'intérieur de la classe et des classes qui en héritent

Voyons comment ces modificateurs fonctionnent en pratique en implémentant un système de gestion des dinosaures à Jurassic Park.

Le modificateur public

Les éléments marqués comme public sont accessibles depuis n'importe où dans le code - aussi bien à l'intérieur de la classe que sur ses instances. C'est le modificateur d'accès par défaut en TypeScript - si tu ne précises aucun modificateur, l'élément sera automatiquement marqué comme public.

1class Dinosaur {
2  // Public properties
3  public name: string;
4  public species: string;
5  public age: number;
6
7  constructor(name: string, species: string, age: number) {
8    this.name = name;
9    this.species = species;
10    this.age = age;
11  }
12
13  // Public method
14  public displayInfo(): void {
15    console.log(`${this.name}: ${this.species}, ${this.age} years old`);
16  }
17}
18
19// Creating an instance
20const rex = new ParkDinosaur("Rex", "Tyrannosaurus", 7);
21
22// Accessing public properties and methods from outside the class
23console.log(rex.name);  // "Rex" - access to public property
24rex.age = 8;  // Modifying a public property
25rex.displayInfo();  // Calling a public method

Les propriétés et méthodes publiques sont comme les informations accessibles à tous dans le parc - par exemple le plan du parc, les horaires d'ouverture ou l'emplacement des toilettes. Tout le monde peut les utiliser sans restriction.

Le modificateur private

Les éléments marqués comme private sont accessibles uniquement à l'intérieur de la classe où ils ont été définis. Cette encapsulation te permet de cacher les détails internes d'implémentation et empêche les modifications accidentelles de l'état depuis l'extérieur de la classe.

1class HealthDiagnosis {
2  // Private properties
3  private id: string;
4  private testResults: Record<string, number>;
5  private notes: string;
6  public healthStatus: "healthy" | "sick" | "critical";
7
8  constructor(id: string) {
9    this.id = id;
10    this.testResults = {};
11    this.notes = "";
12    this.healthStatus = "healthy";
13  }
14
15  // Private method - used only inside the class
16  private analyzeResults(): "healthy" | "sick" | "critical" {
17    // Complex logic for analyzing test results
18    const bodyTemperature = this.testResults["bodyTemperature"] || 37;
19    const leukocyteLevel = this.testResults["leukocyteLevel"] || 7000;
20
21    if (bodyTemperature > 40 || leukocyteLevel > 15000) {
22      return "critical";
23    } else if (bodyTemperature > 38.5 || leukocyteLevel > 11000) {
24      return "sick";
25    } else {
26      return "healthy";
27    }
28  }
29
30  // Public methods using private properties
31  public addTestResult(name: string, value: number): void {
32    this.testResults[name] = value;
33    this.healthStatus = this.analyzeResults();  // Calling a private method
34  }
35
36  public addNote(note: string): void {
37    this.notes += note + "\n";
38  }
39
40  public getReport(): string {
41    return `
42      ID: ${this.id}
43      Health status: ${this.healthStatus}
44      Notes: ${this.notes || "None"}
45    `;
46  }
47}
48
49// Creating an instance
50const raptorDiagnosis = new HealthDiagnosis("VL-001");
51
52// Using the public API
53raptorDiagnosis.addTestResult("bodyTemperature", 39.2);
54raptorDiagnosis.addTestResult("leukocyteLevel", 12500);
55raptorDiagnosis.addNote("Symptoms of viral infection");
56
57console.log(raptorDiagnosis.healthStatus);  // "sick" - access to public property
58console.log(raptorDiagnosis.getReport());  // Calling a public method
59
60// Attempting to access private elements
61// console.log(raptorDiagnosis.id);  // Error: Property 'id' is private
62// console.log(raptorDiagnosis.testResults);  // Error: Property 'testResults' is private
63// raptorDiagnosis.analyzeResults();  // Error: Property 'analyzeResults' is private

Les propriétés et méthodes privées sont comme les procédures réservées du laboratoire de génétique - seul le personnel de laboratoire autorisé (les méthodes de la classe) y a accès, tandis que pour le reste du personnel elles restent cachées pour des raisons de sécurité.

Le modificateur protected

Les éléments marqués comme protected sont accessibles à l'intérieur de la classe où ils ont été définis ainsi que dans les classes qui en héritent. Cela permet de créer des hiérarchies de classes où les classes dérivées ont accès aux éléments protégés de la classe de base, mais où ces éléments restent inaccessibles depuis l'extérieur de la hiérarchie de classes.

1// Base class
2class Animal {
3  // Protected properties
4  protected id: string;
5  protected species: string;
6  protected weight: number;
7  protected health: number;  // 0-100
8
9  constructor(id: string, species: string, weight: number) {
10    this.id = id;
11    this.species = species;
12    this.weight = weight;
13    this.health = 100;
14  }
15
16  // Protected method
17  protected modifyHealth(change: number): void {
18    this.health = Math.max(0, Math.min(100, this.health + change));
19  }
20
21  // Public method
22  public displayStatus(): string {
23    return `${this.species} (${this.id}): Weight: ${this.weight}kg, Health: ${this.health}%`;
24  }
25}
26
27// Inheriting class
28class Dinosaur extends Animal {
29  public name: string;
30  private aggressionLevel: number;  // 0-10
31
32  constructor(id: string, species: string, name: string, weight: number) {
33    super(id, species, weight);
34    this.name = name;
35    this.aggressionLevel = 5;
36  }
37
38  // Method using protected elements from the base class
39  public feed(amount: number): void {
40    this.modifyHealth(amount * 5);  // Access to protected method from base class
41    this.aggressionLevel = Math.max(0, this.aggressionLevel - 1);
42    console.log(`${this.name} has been fed. New health level: ${this.health}%`);
43  }
44
45  public takeDamage(amount: number): void {
46    this.modifyHealth(-amount);  // Access to protected method from base class
47    this.aggressionLevel = Math.min(10, this.aggressionLevel + 2);
48    console.log(`${this.name} took damage. New health level: ${this.health}%`);
49  }
50
51  // Overriding base method using protected properties
52  public displayStatus(): string {
53    return `${this.name} (${this.species}): Weight: ${this.weight}kg, Health: ${this.health}%, Aggression: ${this.aggressionLevel}/10`;
54  }
55}
56
57// Inheriting class with modification of protected property
58class MarineDinosaur extends Animal {
59  private divingDepth: number;
60
61  constructor(id: string, species: string, weight: number, divingDepth: number) {
62    super(id, species, weight);
63    this.divingDepth = divingDepth;
64
65    // Modifying a protected property in the constructor
66    this.health = 90;  // Marine dinosaurs start with lower health
67  }
68
69  public swim(time: number): void {
70    // Using a protected method
71    this.modifyHealth(time > 10 ? -10 : 5);
72
73    console.log(`Marine dinosaur ${this.id} swam for ${time} minutes at a depth of ${this.divingDepth}m`);
74    console.log(`New health level: ${this.health}%`);
75  }
76}
77
78// Creating instances
79const velociraptor = new Dinosaur("VL-002", "Velociraptor", "Blue", 150);
80const mosasaur = new MarineDinosaur("MS-001", "Mosasaurus", 15000, 50);
81
82// Using public methods
83velociraptor.feed(2);
84velociraptor.takeDamage(15);
85console.log(velociraptor.displayStatus());
86
87mosasaur.swim(15);
88console.log(mosasaur.displayStatus());
89
90// Attempting to access protected elements
91// console.log(velociraptor.id);  // Error: Property 'id' is protected
92// console.log(mosasaur.health);  // Error: Property 'health' is protected
93// velociraptor.modifyHealth(50);  // Error: Property 'modifyHealth' is protected

Les propriétés et méthodes protégées sont comme les procédures de sécurité du parc - seuls les employés autorisés et leurs subordonnés (la classe de base et les classes qui en héritent) y ont accès, mais pas les visiteurs du parc (le code externe).

Constructeur et modificateurs d'accès

TypeScript propose une syntaxe spéciale pour les constructeurs qui te permet de déclarer des propriétés directement avec des modificateurs d'accès dans les paramètres du constructeur. C'est une notation abrégée qui évite la répétition de code.

1// Traditional notation
2class ParkEmployee {
3  private id: string;
4  public firstName: string;
5  public lastName: string;
6  protected accessLevel: number;
7
8  constructor(id: string, firstName: string, lastName: string, accessLevel: number) {
9    this.id = id;
10    this.firstName = firstName;
11    this.lastName = lastName;
12    this.accessLevel = accessLevel;
13  }
14}
15
16// Shorthand notation with modifiers in constructor parameters
17class ParkEmployeeShorthand {
18  constructor(
19    private id: string,
20    public firstName: string,
21    public lastName: string,
22    protected accessLevel: number
23  ) {}  // Empty constructor body - everything has already been initialized
24}
25
26// Both notations work identically
27const employee1 = new ParkEmployee("EMP-001", "John", "Hammond", 10);
28const employee2 = new ParkEmployeeShorthand("EMP-002", "Ellie", "Sattler", 8);
29
30console.log(employee1.firstName, employee1.lastName);  // "John Hammond"
31console.log(employee2.firstName, employee2.lastName);  // "Ellie Sattler"
32
33// console.log(employee1.id);  // Error: Property 'id' is private
34// console.log(employee2.id);  // Error: Property 'id' is private

Cette syntaxe abrégée est particulièrement utile dans les classes simples comportant de nombreuses propriétés, car elle réduit la quantité de code répétitif.

Encapsulation et le motif des accesseurs (getters et setters)

L'une des principales raisons d'utiliser les modificateurs d'accès est l'encapsulation - cacher les détails d'implémentation et n'exposer qu'une interface contrôlée pour interagir avec la classe. Une partie de cette approche consiste à utiliser des getters et des setters - des méthodes spéciales qui contrôlent l'accès aux propriétés.

1class EnclosureSecurity {
2  // Private properties
3  private _fenceStatus: "active" | "inactive" | "under_repair";
4  private _batteryChargeLevel: number;  // 0-100
5  private _lastInspection: Date;
6  private _eventLog: string[];
7
8  constructor() {
9    this._fenceStatus = "active";
10    this._batteryChargeLevel = 100;
11    this._lastInspection = new Date();
12    this._eventLog = ["System started: " + new Date().toISOString()];
13  }
14
15  // Getters - controlled read access to private properties
16  get fenceStatus(): "active" | "inactive" | "under_repair" {
17    this.addLog("Fence status read");
18    return this._fenceStatus;
19  }
20
21  get batteryChargeLevel(): number {
22    this.addLog("Battery charge level read");
23    return this._batteryChargeLevel;
24  }
25
26  get lastInspection(): Date {
27    return new Date(this._lastInspection.getTime());  // Defensive copy
28  }
29
30  get securityReport(): string {
31    this.addLog("Security report generated");
32    return `
33      Fence status: ${this._fenceStatus}
34      Battery charge level: ${this._batteryChargeLevel}%
35      Last inspection: ${this._lastInspection.toLocaleDateString()}
36      Number of events in log: ${this._eventLog.length}
37    `;
38  }
39
40  // Setters - controlled write access to private properties
41  set fenceStatus(newStatus: "active" | "inactive" | "under_repair") {
42    this.addLog(`Changed fence status from ${this._fenceStatus} to ${newStatus}`);
43    this._fenceStatus = newStatus;
44
45    if (newStatus === "inactive") {
46      this.startSecurityProcedures();
47    }
48  }
49
50  // This setter contains validation logic
51  set batteryChargeLevel(newLevel: number) {
52    if (newLevel < 0 || newLevel > 100) {
53      throw new Error("Battery charge level must be between 0 and 100");
54    }
55
56    const oldValue = this._batteryChargeLevel;
57    this._batteryChargeLevel = newLevel;
58    this.addLog(`Changed battery charge level from ${oldValue}% to ${newLevel}%`);
59
60    if (newLevel < 20) {
61      this.addLog("WARNING: Low battery level!");
62    }
63  }
64
65  // Private helper method
66  private addLog(message: string): void {
67    this._eventLog.push(`${new Date().toISOString()}: ${message}`);
68  }
69
70  // Private business method
71  private startSecurityProcedures(): void {
72    this.addLog("ALERT: Emergency procedures activated!");
73    // Emergency handling logic
74  }
75
76  // Public method
77  public performInspection(): void {
78    this._lastInspection = new Date();
79    this.addLog("Fence inspection performed");
80    this._batteryChargeLevel = 100;
81    this._fenceStatus = "active";
82  }
83
84  // Public method
85  public getEventLog(): string[] {
86    // We return a copy to prevent modification of the original log
87    return [...this._eventLog];
88  }
89}
90
91// Using the class
92const security = new EnclosureSecurity();
93
94// Using getters
95console.log("Fence status:", security.fenceStatus);
96console.log("Battery level:", security.batteryChargeLevel);
97
98// Using setters
99security.fenceStatus = "under_repair";
100security.batteryChargeLevel = 85;
101
102// Using public methods
103security.performInspection();
104
105// Attempting to set an invalid value
106try {
107  security.batteryChargeLevel = 110;  // Will throw an error
108} catch (e) {
109  console.error(e.message);
110}
111
112// Reading the report and logs
113console.log(security.securityReport);
114console.log("Event history:");
115security.getEventLog().forEach((log, i) => {
116  console.log(`${i + 1}. ${log}`);
117});

L'utilisation des getters et des setters offre un plus grand contrôle sur l'accès aux données :

  • On peut ajouter une logique de validation avant de modifier une valeur
  • On peut effectuer des opérations supplémentaires lors de l'accès aux données
  • On peut renvoyer des copies défensives des objets pour éviter les modifications accidentelles
  • On peut journaliser l'accès aux données

Exemple pratique : système de gestion des dinosaures

Voici un exemple complet d'un système de gestion des dinosaures qui utilise tous les modificateurs d'accès abordés pour créer une architecture orientée objet bien structurée :

1// Types and interfaces
2type DinosaurHealthStatus = "healthy" | "sick" | "in treatment" | "sedated" | "deceased";
3type DietType = "carnivore" | "herbivore" | "omnivore";
4
5interface IMonitoring {
6  startMonitoring(): void;
7  stopMonitoring(): void;
8  getMonitoringStatus(): string;
9}
10
11// Abstract base class
12abstract class ParkAnimal {
13  // Properties with different access modifiers
14  private _id: string;
15  protected _species: string;
16  protected _age: number;
17  protected _weight: number;
18  protected _status: DinosaurHealthStatus;
19  private _creationDate: Date;
20  private _lastUpdate: Date;
21
22  constructor(id: string, species: string, age: number, weight: number) {
23    this._id = id;
24    this._species = species;
25    this._age = age;
26    this._weight = weight;
27    this._status = "healthy";
28    this._creationDate = new Date();
29    this._lastUpdate = new Date();
30  }
31
32  // Getters
33  get id(): string {
34    return this._id;
35  }
36
37  get species(): string {
38    return this._species;
39  }
40
41  get age(): number {
42    return this._age;
43  }
44
45  get weight(): number {
46    return this._weight;
47  }
48
49  get status(): DinosaurHealthStatus {
50    return this._status;
51  }
52
53  // Setter with value checking and date update
54  set status(newStatus: DinosaurHealthStatus) {
55    if (this._status !== newStatus) {
56      this._status = newStatus;
57      this._lastUpdate = new Date();
58      this.logStatusChange(newStatus);
59    }
60  }
61
62  // Protected method accessible to inheriting classes
63  protected updateWeight(newWeight: number): void {
64    if (newWeight > 0) {
65      this._weight = newWeight;
66      this._lastUpdate = new Date();
67    } else {
68      throw new Error("Weight must be greater than zero.");
69    }
70  }
71
72  // Private method accessible only within this class
73  private logStatusChange(newStatus: DinosaurHealthStatus): void {
74    console.log(`[${new Date().toISOString()}] Status change ${this._id}: ${newStatus}`);
75  }
76
77  // Abstract method - every inheriting class must implement it
78  abstract getClinicalDescription(): string;
79
80  // Public method
81  public getBasicData(): string {
82    return `ID: ${this._id} | Species: ${this._species} | Age: ${this._age} years | Weight: ${this._weight} kg | Status: ${this._status}`;
83  }
84}
85
86// Inheriting class
87class ParkDinosaur extends ParkAnimal implements IMonitoring {
88  // Private properties specific to dinosaurs
89  private _diet: DietType;
90  private _aggressionLevel: number; // 1-10
91  private _isMonitored: boolean;
92  private _lastMeal: Date | null;
93  private _assignedEnclosure: string | null;
94
95  constructor(
96    id: string,
97    species: string,
98    age: number,
99    weight: number,
100    diet: DietType,
101    aggressionLevel: number
102  ) {
103    super(id, species, age, weight);
104    this._diet = diet;
105    this._aggressionLevel = this.validateAggressionLevel(aggressionLevel);
106    this._isMonitored = false;
107    this._lastMeal = null;
108    this._assignedEnclosure = null;
109  }
110
111  // Implementation of abstract method
112  getClinicalDescription(): string {
113    let description = `Dinosaur ${this.species} (ID: ${this.id})\n`;
114    description += `Status health: ${this.status}\n`;
115    description += `Age: ${this.age} years, Weight: ${this.weight} kg\n`;
116    description += `Diet: ${this._diet}\n`;
117    description += `Aggression level: ${this._aggressionLevel}/10\n`;
118    description += `Last meal: ${this._lastMeal ? this._lastMeal.toLocaleString() : "No data"}\n`;
119
120    return description;
121  }
122
123  // Implementation of IMonitoring interface
124  startMonitoring(): void {
125    this._isMonitored = true;
126    console.log(`Monitoring started for dinosaur ${this.id}`);
127  }
128
129  stopMonitoring(): void {
130    this._isMonitored = false;
131    console.log(`Monitoring stopped for dinosaur ${this.id}`);
132  }
133
134  getMonitoringStatus(): string {
135    return this._isMonitored ? "Active" : "Inactive";
136  }
137
138  // Public methods specific to dinosaurs
139  public feed(amount: number): void {
140    if (amount <= 0) {
141      throw new Error("Food amount must be greater than zero.");
142    }
143
144    const weightGain = this._diet === "carnivore" ? amount * 0.3 : amount * 0.2;
145    this.updateWeight(this.weight + weightGain);
146
147    this._lastMeal = new Date();
148    this._aggressionLevel = this.validateAggressionLevel(this._aggressionLevel - 1);
149
150    console.log(`Dinosaur ${this.id} was fed. New weight: ${this.weight} kg`);
151  }
152
153  public assignToEnclosure(enclosureCode: string): void {
154    this._assignedEnclosure = enclosureCode;
155    console.log(`Dinosaur ${this.id} was assigned to enclosure ${enclosureCode}`);
156  }
157
158  public sedate(): void {
159    this.status = "sedated";
160    this._aggressionLevel = 1;
161    console.log(`Dinosaur ${this.id} was sedated`);
162  }
163
164  public wake(): void {
165    if (this.status === "sedated") {
166      this.status = "healthy";
167      this._aggressionLevel = this.validateAggressionLevel(this._aggressionLevel + 2);
168      console.log(`Dinosaur ${this.id} was awakened`);
169    }
170  }
171
172  // Getters and setters
173  get diet(): DietType {
174    return this._diet;
175  }
176
177  get aggressionLevel(): number {
178    return this._aggressionLevel;
179  }
180
181  get lastMeal(): Date | null {
182    return this._lastMeal ? new Date(this._lastMeal.getTime()) : null;
183  }
184
185  get assignedEnclosure(): string | null {
186    return this._assignedEnclosure;
187  }
188
189  set aggressionLevel(level: number) {
190    this._aggressionLevel = this.validateAggressionLevel(level);
191  }
192
193  // Private helper method
194  private validateAggressionLevel(level: number): number {
195    return Math.max(1, Math.min(10, level));
196  }
197}
198
199// Using the system
200function testDinosaurManagementSystem() {
201  // Creating dinosaurs
202  const trex = new Dinosaur("T-REX-01", "Tyrannosaurus Rex", 8, 7500, "carnivore", 9);
203  const stegosaurus = new Dinosaur("STEG-01", "Stegosaurus", 12, 5000, "herbivorous", 4);
204
205  // Displaying basic information
206  console.log("=== BASIC DATA ===");
207  console.log(trex.getBasicData());
208  console.log(stegosaurus.getBasicData());
209
210  // Operations on dinosaurs
211  trex.feed(50);
212  trex.assignToEnclosure("CARNIVORE-A");
213  trex.startMonitoring();
214
215  stegosaurus.feed(80);
216  stegosaurus.assignToEnclosure("HERBIVORE-B");
217
218  // Status change
219  trex.status = "in_treatment";
220  stegosaurus.sedate();
221
222  // Getting clinical reports
223  console.log("\n=== CLINICAL REPORTS ===");
224  console.log(trex.getClinicalDescription());
225  console.log(stegosaurus.getClinicalDescription());
226
227  // Monitoring status
228  console.log("\n=== STATUS MONITORING ===");
229  console.log(`T-Rex monitoring: ${trex.getMonitoringStatus()}`);
230  console.log(`Stegosaurus monitoring: ${stegosaurus.getMonitoringStatus()}`);
231
232  // Attempting to access private/protected elements
233  // The following lines would cause a compilation error:
234  // console.log(trex._id);  // Error: Property '_id' is private
235  // console.log(trex._species);  // Error: Property '_species' is protected
236  // trex.logStatusChange("healthy");  // Error: Property 'logStatusChange' is private
237  // trex.validateAggressionLevel(5);  // Error: Property 'validateAggressionLevel' is private
238}
239
240// Running the test
241testDinosaurManagementSystem();

Dans l'exemple ci-dessus :

  1. La classe de base abstraite (ParkAnimal) définit les propriétés et comportements communs à tous les animaux du parc
  2. Les propriétés privées (_id, _creationDate, _lastUpdate) sont accessibles uniquement à l'intérieur de la classe
  3. Les propriétés protégées (_species, _age, _weight, _status) sont accessibles dans la classe de base et les classes qui en héritent
  4. Les getters et setters publics offrent un accès contrôlé aux propriétés
  5. Les méthodes privées (logStatusChange, validateAggressionLevel) servent d'implémentations auxiliaires accessibles uniquement à l'intérieur de la classe
  6. Les méthodes protégées (updateWeight) sont accessibles aux classes qui en héritent
  7. La méthode abstraite (getClinicalDescription) impose une implémentation dans les classes qui en héritent
  8. L'interface (IMonitoring) définit un contrat que la classe Dinosaur doit implémenter

Quand et comment utiliser les modificateurs d'accès

Le choix du modificateur d'accès approprié dépend du cas d'usage précis, mais il existe quelques règles générales :

  1. Utilise private pour :

    • Les détails internes d'implémentation
    • Les données qui ne devraient jamais être accessibles en dehors de la classe
    • Les méthodes auxiliaires qui ne font pas partie de l'API publique
    • Les propriétés qui ne devraient être modifiées qu'au travers de setters contrôlés
  2. Utilise protected pour :

    • Les propriétés et méthodes qui doivent être accessibles dans les classes qui en héritent
    • Les implémentations qui peuvent être étendues ou redéfinies dans les classes dérivées
    • Les données communes à toute la hiérarchie de classes mais qui ne devraient pas être accessibles depuis l'extérieur
  3. Utilise public pour :

    • Les éléments qui constituent l'interface (API publique) de la classe
    • Les méthodes qui doivent être accessibles à tous les utilisateurs de la classe
    • Les propriétés qui peuvent être lues ou modifiées sans risque depuis l'extérieur

Résumé

Les modificateurs d'accès en TypeScript sont un outil clé pour appliquer les bons principes de la conception orientée objet tels que l'encapsulation, l'héritage et le polymorphisme. Ils permettent un contrôle précis de l'accès aux propriétés et aux méthodes d'une classe, ce qui favorise l'écriture d'un code sûr, modulaire et facile à maintenir.

Tout comme à Jurassic Park, où le système de sécurité requiert différents niveaux d'accès, un système orienté objet bien conçu utilise les modificateurs d'accès pour s'assurer que chaque élément n'est accessible qu'à ceux qui doivent réellement y avoir accès.

John Hammond le résumerait en peu de mots : « À Jurassic Park comme en programmation, la sécurité est primordiale. Les modificateurs d'accès sont comme les zones de sécurité du parc - public pour tout le monde, protected pour les spécialistes, et private uniquement pour la haute direction. C'est la clé pour maintenir l'ordre et éviter... les surprises. »

Code de cette leçon : index.ts
1// Access modifiers in TypeScript - Jurassic Park
2console.log("Security system du Parc Jurassique");
3console.log("Demonstration of access modifiers: public, private, protected\n");
4
5// ===========================================
6// 1. Modificateurs d'accès de base
7// ===========================================
8console.log("=== 1. ACCESS MODIFIERS ===");
9
10class BasicDinosaur {
11  // public - accessible from everywhere (default)
12  public name: string;
13  public species: string;
14
15  // private - accessible uniquement dans cette classe
16  private dnaSequence: string;
17  private securityCode: number;
18
19  // protected - accessible dans cette classe et les classes qui en héritent
20  protected age: number;
21  protected habitat: string;
22
23  constructor(name: string, species: string, dnaSequence: string) {
24    this.name = name;
25    this.species = species;
26    this.dnaSequence = dnaSequence;
27    this.securityCode = Math.floor(Math.random() * 10000);
28    this.age = 0;
29    this.habitat = "Enclosure";
30  }
31
32  // Méthode publique
33  public getBasicInfo(): string {
34    return `${this.name} (${this.species})`;
35  }
36
37  // Méthode privée
38  private validateDNA(): boolean {
39    return this.dnaSequence.length > 10;
40  }
41
42  // Méthode protégée
43  protected calculateMaturity(): string {
44    if (this.age < 5) return "Young";
45    if (this.age < 15) return "Adult";
46    return "Old";
47  }
48
49  // Accès public à une fonctionnalité privée
50  public isDNAValid(): boolean {
51    return this.validateDNA();
52  }
53
54  // Méthode avec accès contrôlé aux données privées
55  public getSecureInfo(accessCode: number): string | null {
56    if (accessCode === this.securityCode) {
57      return `DNA: ${this.dnaSequence.substring(0, 5)}...`;
58    }
59    return null;
60  }
61}
62
63const basicDino = new BasicDinosaur("Rex", "T-Rex", "ATCGATCGATCG");
64
65console.log("Basic info:", basicDino.getBasicInfo());
66console.log("Public fields:");
67console.log("  Name:", basicDino.name);
68console.log("  Species:", basicDino.species);
69
70console.log("DNA valid:", basicDino.isDNAValid());
71
72// These won't work - TypeScript compilation errors:
73// console.log(basicDino.dnaSequence); // Error: private
74// console.log(basicDino.age); // Error: protected
75// basicDino.validateDNA(); // Error: private method
76
77console.log("\n");
78
79// ===========================================
80// 2. Héritage et modificateurs
81// ===========================================
82console.log("=== 2. HÉRITAGE ET MODIFICATEURS ===");
83
84class CarnivorousDinosaur extends BasicDinosaur {
85  private huntingExperience: number;
86  protected packSize: number;
87
88  constructor(name: string, species: string, dnaSequence: string) {
89    super(name, species, dnaSequence);
90    this.huntingExperience = 0;
91    this.packSize = 1;
92  }
93
94  // Accès aux champs protégés de la classe de base
95  public setAge(newAge: number): void {
96    this.age = newAge; // OK - protected dans la classe de base
97    console.log(`${this.name} is now ${this.age} ans`);
98  }
99
100  public getMaturityStatus(): string {
101    // Accès à la méthode protégée de la classe de base
102    return `${this.name} est ${this.calculateMaturity()}`;
103  }
104
105  // Redéfinition de la méthode protégée
106  protected calculateMaturity(): string {
107    const baseMaturity = super.calculateMaturity();
108    const huntingLevel = this.huntingExperience > 10 ? "Experienced hunter" : "Beginner";
109    return `${baseMaturity} - ${huntingLevel}`;
110  }
111
112  public hunt(): string {
113    this.huntingExperience++;
114    return `${this.name} is hunting! Experience: ${this.huntingExperience}`;
115  }
116
117  // Impossible d'accéder aux champs privés de la classe de base
118  // public getDNA(): string {
119  //   return this.dnaSequence; // Error: private in base class
120  // }
121}
122
123const carnivoreDino = new CarnivorousDinosaur("Blue", "Velociraptor", "GCTAGCTAGCTA");
124carnivoreDino.setAge(8);
125console.log("Maturity status:", carnivoreDino.getMaturityStatus());
126console.log("Hunting:", carnivoreDino.hunt());
127
128console.log("\n");
129
130// ===========================================
131// 3. Encapsulation des données sensibles
132// ===========================================
133console.log("=== 3. ENCAPSULATION OF SENSITIVE DATA ===");
134
135class SecureDinosaur {
136  public readonly id: string;
137  public name: string;
138
139  private dnaSequence: string;
140  private medicalHistory: string[];
141  private behaviorPatterns: Map<string, number>;
142
143  protected enclosureAccess: boolean;
144  protected emergencyProtocols: string[];
145
146  constructor(id: string, name: string, dna: string) {
147    this.id = id;
148    this.name = name;
149    this.dnaSequence = dna;
150    this.medicalHistory = [];
151    this.behaviorPatterns = new Map();
152    this.enclosureAccess = true;
153    this.emergencyProtocols = ["Tranquilizer", "Emergency Lock"];
154  }
155
156  // Accès contrôlé aux données médicales
157  public addMedicalRecord(record: string, veterinarianCode: string): boolean {
158    if (this.validateVeterinarianAccess(veterinarianCode)) {
159      this.medicalHistory.push(`[${new Date().toISOString()}] ${record}`);
160      return true;
161    }
162    console.log("Unauthorized access to medical records");
163    return false;
164  }
165
166  public getMedicalHistory(veterinarianCode: string): string[] | null {
167    if (this.validateVeterinarianAccess(veterinarianCode)) {
168      return [...this.medicalHistory]; // Copy, pas une référence
169    }
170    console.log("Unauthorized access to medical records");
171    return null;
172  }
173
174  // Accès contrôlé aux schémas de comportement
175  public updateBehaviorPattern(pattern: string, intensity: number, researcherCode: string): boolean {
176    if (this.validateResearcherAccess(researcherCode)) {
177      this.behaviorPatterns.set(pattern, intensity);
178      console.log(`Behavior pattern '${pattern}' updated to ${intensity}`);
179      return true;
180    }
181    console.log("Unauthorized access to behavior data");
182    return false;
183  }
184
185  public getBehaviorAnalysis(researcherCode: string): string | null {
186    if (this.validateResearcherAccess(researcherCode)) {
187      const patterns = Array.from(this.behaviorPatterns.entries());
188      return patterns.map(([pattern, intensity]) => `${pattern}: ${intensity}`).join(", ");
189    }
190    console.log("Unauthorized access to behavior data");
191    return null;
192  }
193
194  // Méthodes de validation privées
195  private validateVeterinarianAccess(code: string): boolean {
196    return code === "VET-2023";
197  }
198
199  private validateResearcherAccess(code: string): boolean {
200    return code === "RESEARCH-2023";
201  }
202
203  // Méthodes de sécurité publiques
204  public getPublicInfo(): object {
205    return {
206      id: this.id,
207      name: this.name,
208      hasAccess: this.enclosureAccess,
209      lastUpdate: new Date().toISOString()
210    };
211  }
212}
213
214const secureDino = new SecureDinosaur("SEC-001", "Alpha", "CLASSIFIED-DNA");
215
216console.log("Public info:", secureDino.getPublicInfo());
217
218// Test du contrôle d'accès
219console.log("\nVeterinary access test:");
220secureDino.addMedicalRecord("Routine checkup", "VET-2023"); // Authorized
221secureDino.addMedicalRecord("Vaccination", "FAKE-CODE"); // Unauthorized
222
223console.log("\nResearch access test:");
224secureDino.updateBehaviorPattern("Aggression", 7, "RESEARCH-2023"); // Authorized
225secureDino.updateBehaviorPattern("Calm", 3, "HACKER-CODE"); // Unauthorized
226
227console.log("\n");
228
229// ===========================================
230// 4. Motif Builder avec modificateurs d'accès
231// ===========================================
232console.log("=== 4. BUILDER PATTERN WITH MODIFIERS ===");
233
234class DinosaurBuilder {
235  private dinosaur: {
236    name?: string;
237    species?: string;
238    diet?: string;
239    weight?: number;
240    dangerLevel?: number;
241  };
242
243  constructor() {
244    this.dinosaur = {};
245  }
246
247  public setName(name: string): DinosaurBuilder {
248    this.dinosaur.name = name;
249    return this;
250  }
251
252  public setSpecies(species: string): DinosaurBuilder {
253    this.dinosaur.species = species;
254    return this;
255  }
256
257  public setDiet(diet: string): DinosaurBuilder {
258    this.dinosaur.diet = diet;
259    return this;
260  }
261
262  public setWeight(weight: number): DinosaurBuilder {
263    this.dinosaur.weight = weight;
264    return this;
265  }
266
267  public setDangerLevel(level: number): DinosaurBuilder {
268    this.dinosaur.dangerLevel = level;
269    return this;
270  }
271
272  // Validation privée
273  private validateDinosaur(): boolean {
274    const required = ['name', 'species', 'diet', 'weight', 'dangerLevel'];
275    return required.every(field => this.dinosaur[field as keyof typeof this.dinosaur] !== undefined);
276  }
277
278  public build(): CompleteDinosaur | null {
279    if (!this.validateDinosaur()) {
280      console.log("Validation failed - missing required fields");
281      return null;
282    }
283
284    return new CompleteDinosaur(
285      this.dinosaur.name!,
286      this.dinosaur.species!,
287      this.dinosaur.diet!,
288      this.dinosaur.weight!,
289      this.dinosaur.dangerLevel!
290    );
291  }
292}
293
294class CompleteDinosaur {
295  public readonly name: string;
296  public readonly species: string;
297  private readonly diet: string;
298  private readonly weight: number;
299  private readonly dangerLevel: number;
300
301  constructor(name: string, species: string, diet: string, weight: number, dangerLevel: number) {
302    this.name = name;
303    this.species = species;
304    this.diet = diet;
305    this.weight = weight;
306    this.dangerLevel = dangerLevel;
307  }
308
309  public getPublicProfile(): object {
310    return {
311      name: this.name,
312      species: this.species
313    };
314  }
315
316  public getDetailedInfo(securityClearance: number): object | null {
317    if (securityClearance >= this.dangerLevel) {
318      return {
319        name: this.name,
320        species: this.species,
321        diet: this.diet,
322        weight: this.weight,
323        dangerLevel: this.dangerLevel
324      };
325    }
326
327    console.log(`Insufficient security clearance. Required: ${this.dangerLevel}, Provided: ${securityClearance}`);
328    return null;
329  }
330}
331
332// Utilisation du motif Builder
333const builder = new DinosaurBuilder();
334const completeDino = builder
335  .setName("Indominus Rex")
336  .setSpecies("Indominus Rex")
337  .setDiet("carnivore")
338  .setWeight(5500)
339  .setDangerLevel(10)
340  .build();
341
342if (completeDino) {
343  console.log("Public profile:", completeDino.getPublicProfile());
344  console.log("\nAccess attempt with clearance 5:", completeDino.getDetailedInfo(5));
345  console.log("Access attempt with clearance 10:", completeDino.getDetailedInfo(10));
346}
347
348console.log("\n");
349
350// ===========================================
351// 5. Système de permissions et de rôles
352// ===========================================
353console.log("=== 5. PERMISSIONS AND ROLES SYSTEM ===");
354
355enum UserRole {
356  Visitor = 1,
357  Staff = 2,
358  Veterinarian = 3,
359  Researcher = 4,
360  Security = 5,
361  Administrator = 6
362}
363
364abstract class ParkUser {
365  protected name: string;
366  protected role: UserRole;
367  protected accessLevel: number;
368
369  constructor(name: string, role: UserRole) {
370    this.name = name;
371    this.role = role;
372    this.accessLevel = role;
373  }
374
375  public getName(): string {
376    return this.name;
377  }
378
379  public getRole(): string {
380    return UserRole[this.role];
381  }
382
383  // Méthode abstraite - doit être implémentée dans les classes dérivées
384  public abstract canAccess(resource: string): boolean;
385
386  protected hasMinimumAccess(requiredLevel: number): boolean {
387    return this.accessLevel >= requiredLevel;
388  }
389}
390
391class ParkStaff extends ParkUser {
392  private department: string;
393
394  constructor(name: string, role: UserRole, department: string) {
395    super(name, role);
396    this.department = department;
397  }
398
399  public canAccess(resource: string): boolean {
400    const resourcePermissions: { [key: string]: number } = {
401      "public_areas": UserRole.Visitor,
402      "staff_rooms": UserRole.Staff,
403      "dinosaur_basic_info": UserRole.Staff,
404      "medical_records": UserRole.Veterinarian,
405      "research_data": UserRole.Researcher,
406      "security_systems": UserRole.Security,
407      "admin_panel": UserRole.Administrator
408    };
409
410    const requiredLevel = resourcePermissions[resource];
411    if (requiredLevel === undefined) {
412      console.log(`Unknown resource: ${resource}`);
413      return false;
414    }
415
416    const hasAccess = this.hasMinimumAccess(requiredLevel);
417    console.log(`${this.name} (${this.getRole()}) accessing ${resource}: ${hasAccess ? 'GRANTED' : 'DENIED'}`);
418    return hasAccess;
419  }
420
421  public getDepartment(): string {
422    return this.department;
423  }
424}
425
426// Test du système de permissions
427const visitor = new ParkStaff("John Doe", UserRole.Visitor, "Tourist");
428const veterinarian = new ParkStaff("Dr. Sarah Connor", UserRole.Veterinarian, "Medical");
429const researcher = new ParkStaff("Dr. Alan Grant", UserRole.Researcher, "Paleontology");
430const admin = new ParkStaff("Robert Muldoon", UserRole.Administrator, "Operations");
431
432console.log("\nAccess control test:");
433
434const resources = ["public_areas", "dinosaur_basic_info", "medical_records", "research_data", "admin_panel"];
435const users = [visitor, veterinarian, researcher, admin];
436
437users.forEach(user => {
438  console.log(`\n${user.getName()} (${user.getRole()}):`);
439  resources.forEach(resource => {
440    user.canAccess(resource);
441  });
442});
443
444console.log("\n");
445
446// ===========================================
447// 6. Getters et setters avec contrôle d'accès
448// ===========================================
449console.log("=== 6. GETTERS AND SETTERS WITH ACCESS CONTROL ===");
450
451class MonitoredDinosaur {
452  private _name: string;
453  private _health: number;
454  private _feedingSchedule: Date[];
455  private _isQuarantined: boolean;
456  private _lastHealthCheck: Date;
457
458  constructor(name: string) {
459    this._name = name;
460    this._health = 100;
461    this._feedingSchedule = [];
462    this._isQuarantined = false;
463    this._lastHealthCheck = new Date();
464  }
465
466  // Getter avec journalisation des accès
467  get name(): string {
468    console.log(`Access logged: Reading name of ${this._name}`);
469    return this._name;
470  }
471
472  // Getter avec validation des permissions
473  get health(): number {
474    console.log(`Health data accessed for ${this._name}`);
475    return this._health;
476  }
477
478  // Setter avec validation
479  set health(value: number) {
480    if (value < 0 || value > 100) {
481      console.log(`Invalid health value: ${value}. Must be 0-100`);
482      return;
483    }
484
485    const oldHealth = this._health;
486    this._health = value;
487    this._lastHealthCheck = new Date();
488
489    console.log(`Health updated for ${this._name}: ${oldHealth} → ${value}`);
490
491    // Mise en quarantaine automatique en cas de faible santé
492    if (value < 30 && !this._isQuarantined) {
493      this._isQuarantined = true;
494      console.log(`${this._name} automatically quarantined due to low health`);
495    }
496  }
497
498  // Propriété calculée
499  get status(): string {
500    if (this._isQuarantined) return "Quarantined";
501    if (this._health < 30) return "Critical";
502    if (this._health < 60) return "Poor";
503    if (this._health < 80) return "Good";
504    return "Excellent";
505  }
506
507  // Setter avec logique métier
508  set isQuarantined(value: boolean) {
509    if (value !== this._isQuarantined) {
510      this._isQuarantined = value;
511      console.log(`${this._name} quarantine status: ${value ? 'ACTIVE' : 'INACTIVE'}`);
512
513      if (!value && this._health < 50) {
514        console.log(`Warning: Releasing ${this._name} from quarantine with health ${this._health}`);
515      }
516    }
517  }
518
519  get isQuarantined(): boolean {
520    return this._isQuarantined;
521  }
522
523  // Méthode avec accès contrôlé à l'historique
524  public getFeedingHistory(accessLevel: number): Date[] | null {
525    if (accessLevel < UserRole.Staff) {
526      console.log(`Insufficient access to feeding history`);
527      return null;
528    }
529
530    return [...this._feedingSchedule];
531  }
532
533  public scheduleFeed(time: Date): void {
534    this._feedingSchedule.push(time);
535    console.log(`Feeding scheduled for ${this._name} at ${time.toLocaleTimeString()}`);
536  }
537}
538
539const monitoredDino = new MonitoredDinosaur("Alpha-7");
540
541console.log("\nMonitored dinosaur test:");
542console.log("Name:", monitoredDino.name);
543console.log("Initial health:", monitoredDino.health);
544console.log("Initial status:", monitoredDino.status);
545
546// Test des setters
547monitoredDino.health = 25; // Automatic quarantine
548console.log("Status after health drop:", monitoredDino.status);
549
550monitoredDino.health = 85; // Health improvement
551console.log("Status after recovery:", monitoredDino.status);
552
553monitoredDino.isQuarantined = false; // Manual release from quarantine
554
555// Test du calendrier d'alimentation
556monitoredDino.scheduleFeed(new Date(Date.now() + 3600000)); // In one hour
557
558console.log("\n");
559
560// ===========================================
561// 7. Résumé des modificateurs d'accès
562// ===========================================
563console.log("=== 7. ACCESS MODIFIERS SUMMARY ===");
564
565console.log("PUBLIC:");
566console.log("  • Accessible from anywhere");
567console.log("  • Default modifier in TypeScript");
568console.log("  • Use for the class public API\n");
569
570console.log("PRIVATE:");
571console.log("  • Accessible only within the same class");
572console.log("  • Implementation encapsulation");
573console.log("  • Sensitive data protection\n");
574
575console.log("PROTECTED:");
576console.log("  • Accessible in the class and inheriting classes");
577console.log("  • Sharing between class hierarchy");
578console.log("  • Controlled access in inheritance\n");
579
580console.log("READONLY:");
581console.log("  • Can only be assigned in the constructor");
582console.log("  • Protection against modification");
583console.log("  • Immutable fields\n");
584
585console.log("Access modifiers ensure security in Jurassic Park!");

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Quel modificateur d'accès en TypeScript autorise l'accès à une propriété uniquement à l'intérieur de la classe et de ses classes dérivées ?

Articles utiles