Cours JavaScript et TypeScript · Module 7: TypeScript avancé
Propriétés statiques et abstraites
Dans cette leçon4
« À Jurassic Park, nous avons des protocoles et des standards qui s'appliquent à tous les objets d'une même classe », explique le Dr Henry Wu, généticien en chef du parc. « Par exemple, tous nos enclos de dinosaures suivent la même procédure de sécurité, peu importe l'enclos précis dont on parle. En revanche, chaque espèce de dinosaure possède son propre patron d'ADN unique, mais nous savons tous que l'ADN doit exister - même si les détails diffèrent d'une espèce à l'autre. »
En programmation orientée objet, TypeScript propose deux concepts puissants qui reflètent ces idées :
- Les propriétés et méthodes statiques - partagées par toutes les instances d'une classe
- Les propriétés et méthodes abstraites - qui doivent être implémentées par les classes qui en héritent
Dans cet exercice, nous allons explorer en profondeur ces concepts en créant un système avancé de gestion de la génétique et des protocoles de sécurité pour Jurassic Park.
Propriétés et méthodes statiques
Les éléments statiques d'une classe appartiennent à la classe elle-même, et non à ses instances. Cela signifie que :
- Il n'existe qu'une seule copie de chaque propriété statique, quel que soit le nombre d'instances créées
- L'accès aux propriétés statiques se fait via le nom de la classe, et non via une instance
- Les méthodes statiques peuvent être appelées directement sur la classe
Utilisation de base des éléments statiques
Voyons comment les éléments statiques peuvent être utilisés dans le contexte de Jurassic Park :
1class SecurityProtocol {
2 // Static property - shared by all instances
3 static readonly LEVEL_CRITICAL: number = 5;
4 static readonly LEVEL_HIGH: number = 4;
5 static readonly LEVEL_MEDIUM: number = 3;
6 static readonly LEVEL_LOW: number = 2;
7 static readonly LEVEL_MINIMAL: number = 1;
8
9 // Static property storing the current global park threat level
10 private static _currentThreatLevel: number = SecurityProtocol.LEVEL_LOW;
11
12 // Instance properties
13 private readonly area: string;
14 private localThreatLevel: number;
15
16 constructor(area: string) {
17 this.area = area;
18 this.localThreatLevel = SecurityProtocol._currentThreatLevel;
19 console.log(`Created security protocol for area: ${area}`);
20 }
21
22 // Static method accessible directly on the class
23 static raiseGlobalThreatLevel(): void {
24 if (this._currentThreatLevel < this.LEVEL_CRITICAL) {
25 this._currentThreatLevel++;
26 console.log(`WARNING: Raised global threat level to ${this._currentThreatLevel}`);
27
28 // Calling another static method
29 this.notifyAboutThreatLevel();
30 } else {
31 console.log("WARNING: Maximum threat level already reached!");
32 }
33 }
34
35 // Another static method
36 static lowerGlobalThreatLevel(): void {
37 if (this._currentThreatLevel > this.LEVEL_MINIMAL) {
38 this._currentThreatLevel--;
39 console.log(`Lowered global threat level to ${this._currentThreatLevel}`);
40 } else {
41 console.log("Threat level is already at minimum.");
42 }
43 }
44
45 // Static getter
46 static get currentThreatLevel(): number {
47 return this._currentThreatLevel;
48 }
49
50 // Private static method
51 private static notifyAboutThreatLevel(): void {
52 console.log(`Notification: Current park threat level: ${this._currentThreatLevel}`);
53 }
54
55 // Instance method using static properties
56 updateLocalThreatLevel(): void {
57 // Updating the local level based on the global level
58 this.localThreatLevel = SecurityProtocol._currentThreatLevel;
59 console.log(`Area ${this.area}: updated threat level to ${this.localThreatLevel}`);
60 }
61
62 get securityStatus(): string {
63 const levels = [
64 "UNKNOWN",
65 "MINIMAL",
66 "LOW",
67 "MEDIUM",
68 "HIGH",
69 "CRITICAL"
70 ];
71
72 return `Area: ${this.area}, Threat level: ${levels[this.localThreatLevel]}`;
73 }
74}
75
76// Using static properties and methods - without creating instances!
77console.log("Default threat level:", SecurityProtocol.currentThreatLevel);
78
79// Calling a static method directly on the class
80SecurityProtocol.raiseGlobalThreatLevel();
81SecurityProtocol.raiseGlobalThreatLevel();
82
83// Creating instances of the class
84const centralProtocol = new SecurityProtocol("Operations Center");
85const enclosureProtocol = new SecurityProtocol("T-Rex Enclosure");
86
87// Displaying the security status
88console.log(centralProtocol.securityStatus);
89console.log(enclosureProtocol.securityStatus);
90
91// Raising the global threat level again
92SecurityProtocol.raiseGlobalThreatLevel();
93
94// Updating local threat levels
95centralProtocol.updateLocalThreatLevel();
96enclosureProtocol.updateLocalThreatLevel();
97
98// Checking updated statuses
99console.log(centralProtocol.securityStatus);
100console.log(enclosureProtocol.securityStatus);Dans l'exemple ci-dessus :
- Les constantes de niveau de menace sont des propriétés statiques, accessibles directement via la classe
- La propriété statique
_currentThreatLevelest partagée entre toutes les instances - Les méthodes statiques peuvent modifier les propriétés statiques et appeler d'autres méthodes statiques
- Les instances de la classe peuvent mettre à jour leur état interne en fonction des propriétés statiques
Quand utiliser les éléments statiques
Les éléments statiques sont particulièrement utiles dans les cas suivants :
- Constantes et configuration - Des valeurs qui restent inchangées pour toutes les instances d'une classe
- État global - Lorsqu'on a besoin d'une valeur partagée par toutes les instances
- Méthodes utilitaires - Des fonctions auxiliaires qui n'ont pas besoin de l'état d'une instance
- Méthodes fabriques - Des façons alternatives de créer des instances de la classe
Voici un exemple plus élaboré montrant différents usages des éléments statiques :
1class GeneticRegistry {
2 // Static constants
3 static readonly NUCLEOTIDE_BASE = ["A", "C", "G", "T"];
4 static readonly MIN_DNA_LENGTH = 1000000;
5
6 // Global state
7 private static _registeredSpeciesCount: number = 0;
8 private static _speciesRegistrations: Map<string, string> = new Map();
9
10 // Instance properties
11 private id: string;
12 private dnaSequence: string;
13 private name: string;
14
15 constructor(name: string, dnaSequence: string) {
16 if (dnaSequence.length < GeneticRegistry.MIN_DNA_LENGTH) {
17 throw new Error(`DNA sequence is too short. Minimum required: ${GeneticRegistry.MIN_DNA_LENGTH} base pairs.`);
18 }
19
20 this.name = name;
21 this.dnaSequence = dnaSequence;
22 this.id = GeneticRegistry.generateId(name);
23
24 // Registering a new species
25 GeneticRegistry._speciesRegistrations.set(this.id, name);
26 GeneticRegistry._registeredSpeciesCount++;
27 }
28
29 // Utility method - creates an ID based on name
30 private static generateId(name: string): string {
31 const timestamp = Date.now();
32 const cleanName = name.toLowerCase().replace(/[^a-z0-9]/g, "");
33 return `DNO-${cleanName}-${timestamp}`;
34 }
35
36 // Factory method - creates an instance with random DNA
37 static createTestSpecies(name: string): GeneticRegistry {
38 const dnaLength = this.MIN_DNA_LENGTH + Math.floor(Math.random() * 500000);
39 let sequence = "";
40
41 for (let i = 0; i < dnaLength; i++) {
42 const randomIndex = Math.floor(Math.random() * this.NUCLEOTIDE_BASE.length);
43 sequence += this.NUCLEOTIDE_BASE[randomIndex];
44 }
45
46 return new GeneticRegistry(name, sequence);
47 }
48
49 // Utility method - verifies the correctness of a DNA sequence
50 static verifySequence(sequence: string): boolean {
51 if (sequence.length < this.MIN_DNA_LENGTH) return false;
52
53 // Checks if the sequence contains only allowed nucleotides
54 for (const nucleotide of sequence) {
55 if (!this.NUCLEOTIDE_BASE.includes(nucleotide)) {
56 return false;
57 }
58 }
59
60 return true;
61 }
62
63 // Getter for the static property
64 static get registeredSpeciesCount(): number {
65 return this._registeredSpeciesCount;
66 }
67
68 // Static method returning all registered species
69 static getAllSpecies(): string[] {
70 return Array.from(this._speciesRegistrations.values());
71 }
72
73 // Instance method using static fields
74 analyzeSequence(): { proportions: Record<string, number>, length: number } {
75 const proportions: Record<string, number> = {};
76
77 // Initializing counters for each nucleotide
78 GeneticRegistry.NUCLEOTIDE_BASE.forEach(n => {
79 proportions[n] = 0;
80 });
81
82 // Counting nucleotides
83 for (const nucleotide of this.dnaSequence) {
84 proportions[nucleotide]++;
85 }
86
87 // Converting to percentages
88 GeneticRegistry.NUCLEOTIDE_BASE.forEach(n => {
89 proportions[n] = Number((proportions[n] / this.dnaSequence.length * 100).toFixed(2));
90 });
91
92 return {
93 proportions,
94 length: this.dnaSequence.length
95 };
96 }
97
98 // Instance getters
99 get id(): string {
100 return this.id;
101 }
102
103 get speciesName(): string {
104 return this.name;
105 }
106}
107
108// Using static methods and properties
109console.log("Minimum DNA length:", GeneticRegistry.MIN_DNA_LENGTH);
110console.log("Available nucleotides:", GeneticRegistry.NUCLEOTIDE_BASE.join(", "));
111
112// Verifying a sample sequence
113const exampleSequence = "ACGTACGTACGT".repeat(100000); // Repeating to reach min. length
114console.log("Sequence verification:", GeneticRegistry.verifySequence(exampleSequence));
115
116// Creating instances using the factory method
117const trex = GeneticRegistry.createTestSpecies("Tyrannosaurus Rex");
118const raptor = GeneticRegistry.createTestSpecies("Velociraptor");
119const trike = GeneticRegistry.createTestSpecies("Triceratops");
120
121// Checking how many species have been registered
122console.log("Number of registered species:", GeneticRegistry.registeredSpeciesCount);
123console.log("Species list:", GeneticRegistry.getAllSpecies());
124
125// Using instance methods
126const trexAnalysis = trex.analyzeSequence();
127console.log(`Analysis: ${trex.speciesName} (ID: ${trex.id}):`);
128console.log(`Sequence length: ${trexAnalysis.length} base pairs`);
129console.log("Nucleotide proportions:", trexAnalysis.proportions);Cet exemple illustre :
- L'utilisation de constantes statiques (
NUCLEOTIDE_BASE,MIN_DNA_LENGTH) - Un état global qui suit le nombre et l'enregistrement des espèces
- Des méthodes utilitaires statiques comme
verifySequenceetgenerateId - Une méthode fabrique
createTestSpeciesqui crée des instances avec des données aléatoires - Comment les méthodes d'instance peuvent utiliser les propriétés statiques
Classes et méthodes abstraites
Les classes abstraites en TypeScript définissent un modèle qui doit être implémenté par les classes qui en héritent. On ne peut pas créer directement une instance d'une classe abstraite - elle sert de « plan » de base pour d'autres classes.
Les méthodes abstraites, définies dans une classe abstraite, doivent être implémentées par les classes qui en héritent. Elles définissent un contrat précisant quelles méthodes doivent être disponibles, mais ne contiennent pas leur propre implémentation.
Les bases des classes abstraites
Voyons comment utiliser les classes abstraites dans le contexte de Jurassic Park :
1// Abstract base class for all dinosaurs
2abstract class Dinosaur {
3 // Properties common to all dinosaurs
4 private _id: string;
5 protected _name: string;
6 protected _species: string;
7 protected _age: number;
8 protected _weight: number;
9
10 constructor(id: string, name: string, species: string, age: number, weight: number) {
11 this._id = id;
12 this._name = name;
13 this._species = species;
14 this._age = age;
15 this._weight = weight;
16 }
17
18 // Getters
19 get id(): string { return this._id; }
20 get name(): string { return this._name; }
21 get species(): string { return this._species; }
22 get age(): number { return this._age; }
23 get weight(): number { return this._weight; }
24
25 // Methods implemented in all derived classes
26 abstract calculateCaloricNeeds(): number;
27 abstract determineThreatLevel(): number;
28
29 // Abstract property - must be implemented in derived classes
30 abstract get dietType(): string;
31
32 // Concrete method (not abstract) - available in all derived classes
33 getBasicData(): string {
34 return `${this._name} (${this._species}) - ID: ${this._id}, Age: ${this._age} years, Weight: ${this._weight} kg`;
35 }
36
37 // Method using abstract methods
38 generateReportDinosaur(): string {
39 const calories = this.calculateCaloricNeeds();
40 const threatLevel = this.determineThreatLevel();
41
42 return `
43=== DINOSAUR REPORT ===
44${this.getBasicData()}
45Diet type: ${this.dietType}
46Daily caloric demand: ${calories} kcal
47Threat level: ${threatLevel}/10
48======================
49 `;
50 }
51}
52
53// Class inheriting from the abstract class
54class PredatoryDinosaur extends Dinosaur {
55 private _biteForce: number;
56 private _runSpeed: number;
57
58 constructor(
59 id: string,
60 name: string,
61 species: string,
62 age: number,
63 weight: number,
64 biteForce: number,
65 runSpeed: number
66 ) {
67 super(id, name, species, age, weight);
68 this._biteForce = biteForce;
69 this._runSpeed = runSpeed;
70 }
71
72 // Getters
73 get biteForce(): number { return this._biteForce; }
74 get runSpeed(): number { return this._runSpeed; }
75
76 // Implementation of abstract property
77 get dietType(): string {
78 return "carnivore";
79 }
80
81 // Implementations of abstract methods
82 calculateCaloricNeeds(): number {
83 // For predators we use a complex formula
84 return Math.round(this._weight * 0.6 * (this._runSpeed * 0.2));
85 }
86
87 determineThreatLevel(): number {
88 // Threat level depends on bite force, speed, and weight
89 const base = (this._biteForce * 0.7 + this._runSpeed * 0.3) / 100;
90 return Math.min(10, Math.max(1, Math.round(base)));
91 }
92
93 // Method specific to predators
94 hunt(): string {
95 return `${this._name} hunts at speed ${this._runSpeed} km/h and bite force ${this._biteForce} PSI`;
96 }
97}
98
99// Another inheriting class
100class HerbivorousDinosaur extends Dinosaur {
101 private _neckLength: number;
102 private _armorType: "none" | "light" | "medium" | "heavy";
103
104 constructor(
105 id: string,
106 name: string,
107 species: string,
108 age: number,
109 weight: number,
110 neckLength: number,
111 armorType: "none" | "light" | "medium" | "heavy"
112 ) {
113 super(id, name, species, age, weight);
114 this._neckLength = neckLength;
115 this._armorType = armorType;
116 }
117
118 // Getters
119 get neckLength(): number { return this._neckLength; }
120 get armorType(): string { return this._armorType; }
121
122 // Implementation of abstract property
123 get dietType(): string {
124 return "herbivorous";
125 }
126
127 // Implementations of abstract methods
128 calculateCaloricNeeds(): number {
129 // For herbivores we use a simpler formula - they need more calories per kilogram
130 return Math.round(this._weight * 0.8);
131 }
132
133 determineThreatLevel(): number {
134 // Threat level is lower for herbivores, but depends on weight and armor type
135 let base = this._weight / 1000;
136
137 // Modifier dependent on armor
138 const armorModifiers = {
139 "none": 0,
140 "light": 1,
141 "medium": 2,
142 "heavy": 3
143 };
144
145 base += armorModifiers[this._armorType];
146
147 return Math.min(7, Math.max(1, Math.round(base))); // Max. 7 for herbivores
148 }
149
150 // Method specific to herbivores
151 graze(): string {
152 const reachHeight = this._neckLength > 2 ? "tall" : "low";
153 return `${this._name} is grazing, eating plants from ${reachHeight} tree sections`;
154 }
155}
156
157// We cannot create an instance of an abstract class
158// const invalid = new Dinosaur("ID", "Name", "Species", 1, 100); // Error!
159
160// Creating instances of derived classes
161const trex = new PredatoryDinosaur("T-REX-1", "Rex", "Tyrannosaurus", 8, 7000, 12800, 32);
162const stegosaurus = new HerbivorousDinosaur("STEG-1", "Spike", "Stegosaurus", 12, 5000, 1.2, "heavy");
163const diplodocus = new HerbivorousDinosaur("DIPL-1", "Dippy", "Diplodocus", 15, 16000, 6.5, "none");
164
165// Using methods implemented in derived classes
166console.log(trex.generateReportDinosaur());
167console.log(stegosaurus.generateReportDinosaur());
168console.log(diplodocus.generateReportDinosaur());
169
170// Using methods specific to a given class
171console.log(trex.hunt());
172console.log(stegosaurus.graze());
173
174// For both types of dinosaurs we can use base class methods
175console.log(trex.getBasicData());
176console.log(diplodocus.getBasicData());Dans l'exemple ci-dessus :
Dinosaurest une classe abstraite contenant des propriétés et méthodes communes- Les méthodes abstraites
calculateCaloricNeeds()etdetermineThreatLevel()doivent être implémentées par les classes qui en héritent - La propriété abstraite
dietTypedoit être définie dans les classes dérivées - Les classes
PredatoryDinosauretHerbivorousDinosaurimplémentent les méthodes et propriétés abstraites - On ne peut pas créer directement une instance de la classe
Dinosaur
Applications des classes et méthodes abstraites
Les classes abstraites sont particulièrement utiles dans les cas suivants :
- Définir une interface commune - Spécifier un ensemble cohérent de méthodes pour un groupe de classes apparentées
- Partager du code - Fournir une implémentation commune à des groupes de classes
- Imposer un contrat - Garantir que toutes les classes dérivées implémenteront les méthodes requises
- Créer des hiérarchies de types - Construire des relations complexes entre types
Voyons un exemple plus avancé qui combine classes et méthodes abstraites avec des éléments statiques dans un système de gestion des protocoles de sécurité de Jurassic Park :
1// Helper types
2type AlertLevel = "normall" | "elevated" | "high" | "critical";
3type StatusSystem = "active" | "inactive" | "maintenance" | "failure";
4
5// Interface for monitoring systems
6interface MonitoringSystem {
7 startMonitoring(): void;
8 stopMonitoring(): void;
9 getCurrentStatus(): StatusSystem;
10}
11
12// Abstract base class for all security systems
13abstract class SecuritySystem implements MonitoringSystem {
14 // Static properties - for all security systems
15 private static _parkAlertLevel: AlertLevel = "normall";
16 private static _activeSystems: Set<string> = new Set();
17 private static _incidents: Array<{date: Date, systemType: string, location: string, description: string}> = [];
18
19 // Regular properties
20 protected _id: string;
21 protected _location: string;
22 protected _status: StatusSystem;
23 protected _isMonitored: boolean;
24 protected _reviewDate: Date;
25
26 constructor(id: string, location: string) {
27 this._id = id;
28 this._location = location;
29 this._status = "inactive";
30 this._isMonitored = false;
31 this._reviewDate = new Date();
32
33 // System registration
34 SecuritySystem.registerSystem(id, this.constructor.name);
35 }
36
37 // Static methods
38 static registerSystem(id: string, systemType: string): void {
39 console.log(`Registered new security system: ${systemType} (ID: ${id})`);
40 }
41
42 static reportIncident(systemType: string, location: string, description: string): void {
43 const incident = {
44 date: new Date(),
45 systemType,
46 location,
47 description
48 };
49
50 this._incidents.push(incident);
51 console.log(`ALERT: Incident reported at location ${location}: ${description}`);
52
53 // Automatically raising the alert level
54 if (this._parkAlertLevel === "normall") {
55 this.changeAlertLevel("elevated");
56 } else if (this._parkAlertLevel === "elevated") {
57 this.changeAlertLevel("high");
58 } else if (this._parkAlertLevel === "high") {
59 this.changeAlertLevel("critical");
60 }
61 }
62
63 static changeAlertLevel(newLevel: AlertLevel): void {
64 this._parkAlertLevel = newLevel;
65 console.log(`WARNING: Changed park alert level to: ${newLevel.toUpperCase()}`);
66 }
67
68 static get alertLevel(): AlertLevel {
69 return this._parkAlertLevel;
70 }
71
72 static get recentIncidents(): Array<{date: Date, systemType: string, location: string, description: string}> {
73 // Returning the 5 most recent incidents
74 return [...this._incidents].slice(-5);
75 }
76
77 // Instance getters
78 get id(): string { return this._id; }
79 get location(): string { return this._location; }
80 get status(): StatusSystem { return this._status; }
81
82 // Implementation of MonitoringSystem interface
83 startMonitoring(): void {
84 this._isMonitored = true;
85 SecuritySystem._activeSystems.add(this._id);
86 console.log(`Monitoring started for system ${this.constructor.name} in location ${this._location}`);
87 }
88
89 stopMonitoring(): void {
90 this._isMonitored = false;
91 SecuritySystem._activeSystems.delete(this._id);
92 console.log(`Monitoring stopped for system ${this.constructor.name} in location ${this._location}`);
93 }
94
95 getCurrentStatus(): StatusSystem {
96 return this._status;
97 }
98
99 // Abstract methods that must be implemented
100 abstract start(): void;
101 abstract stop(): void;
102 abstract performReview(): void;
103
104 // Abstract properties
105 abstract get systemType(): string;
106 abstract get alarmPriorityLevel(): number; // 1-10
107
108 // Method using abstract properties and methods
109 generateStatusReport(): string {
110 return `
111=== SECURITY SYSTEM REPORT ===
112ID: ${this._id}
113Type: ${this.systemType}
114Location: ${this._location}
115Status: ${this._status}
116Monitored: ${this._isMonitored ? "YES" : "NO"}
117Alarm priority: ${this.alarmPriorityLevel}/10
118Last review: ${this._reviewDate.toLocaleDateString()}
119Global alert level: ${SecuritySystem._parkAlertLevel.toUpperCase()}
120=================================
121 `;
122 }
123}
124
125// Derived class - Electric Fence System
126class ElectricFenceSystem extends SecuritySystem {
127 private _voltage: number; // in volts
128 private _fenceLength: number; // in meters
129 private _securityType: "standard" | "reinforced" | "ultra-reinforced";
130
131 constructor(
132 id: string,
133 location: string,
134 fenceLength: number,
135 securityType: "standard" | "reinforced" | "ultra-reinforced"
136 ) {
137 super(id, location);
138 this._voltage = 0;
139 this._fenceLength = fenceLength;
140 this._securityType = securityType;
141 }
142
143 // Implementation of abstract properties
144 get systemType(): string {
145 return "Fence Electric";
146 }
147
148 get alarmPriorityLevel(): number {
149 // Priority depends on the security type
150 const base = this._securityType === "standard" ? 7 :
151 this._securityType === "reinforced" ? 8 : 10;
152
153 // We take fence length into account - longer ones have higher priority
154 const modifier = Math.min(2, Math.floor(this._fenceLength / 1000));
155
156 return Math.min(10, base + modifier);
157 }
158
159 // Implementation of abstract methods
160 start(): void {
161 if (this._status === "maintenance") {
162 console.log(`ERROR: Cannot start system ${this._id} - is under maintenance`);
163 return;
164 }
165
166 this._status = "active";
167
168 // Voltage depends on the security type
169 this._voltage = this._securityType === "standard" ? 10000 :
170 this._securityType === "reinforced" ? 20000 : 30000;
171
172 console.log(`Started fence system ${this._id} in location ${this._location}`);
173 console.log(`Voltage: ${this._voltage}V, Length: ${this._fenceLength}m`);
174 }
175
176 stop(): void {
177 this._status = "inactive";
178 this._voltage = 0;
179 console.log(`Stopped fence system ${this._id} in location ${this._location}`);
180
181 // Reporting an incident at high alert level
182 if (SecuritySystem.alertLevel === "high" || SecuritySystem.alertLevel === "critical") {
183 SecuritySystem.reportIncident(
184 this.systemType,
185 this._location,
186 `Fence shutdown at high alert level!`
187 );
188 }
189 }
190
191 performReview(): void {
192 const previousStatus = this._status;
193 this._status = "maintenance";
194 console.log(`Started fence system review ${this._id}`);
195
196 // Simulation of inspection
197 setTimeout(() => {
198 this._reviewDate = new Date();
199 this._status = previousStatus;
200 console.log(`Completed fence system review ${this._id}`);
201 }, 2000);
202 }
203
204 // Methods specific to this system type
205 changeVoltage(newVoltage: number): void {
206 if (this._status !== "active") {
207 console.log(`ERROR: Cannot change voltage - system is not active`);
208 return;
209 }
210
211 this._voltage = newVoltage;
212 console.log(`Changed fence voltage ${this._id} to ${newVoltage}V`);
213 }
214
215 reportFenceBreach(): void {
216 this._status = "failure";
217 SecuritySystem.reportIncident(
218 this.systemType,
219 this._location,
220 `Electric fence breach detected!`
221 );
222 }
223}
224
225// Another derived class - Video Monitoring System
226class VideoMonitoringSystem extends SecuritySystem {
227 private _cameraCount: number;
228 private _resolution: "SD" | "HD" | "4K";
229 private _nightMode: boolean;
230
231 constructor(
232 id: string,
233 location: string,
234 countKamer: number,
235 resolution: "SD" | "HD" | "4K"
236 ) {
237 super(id, location);
238 this._cameraCount = countKamer;
239 this._resolution = resolution;
240 this._nightMode = false;
241 }
242
243 // Implementation of abstract properties
244 get systemType(): string {
245 return "Video monitoring";
246 }
247
248 get alarmPriorityLevel(): number {
249 // Priority depends on the number of cameras and resolution
250 const base = 5;
251
252 // Resolution modifier
253 const resolutionModifier =
254 this._resolution === "SD" ? 0 :
255 this._resolution === "HD" ? 1 : 2;
256
257 // Camera count modifier
258 const cameraModifier = Math.min(2, Math.floor(this._cameraCount / 10));
259
260 return Math.min(10, base + resolutionModifier + cameraModifier);
261 }
262
263 // Implementation of abstract methods
264 start(): void {
265 this._status = "active";
266 console.log(`Started monitoring system ${this._id} in location ${this._location}`);
267 console.log(`Camera count: ${this._cameraCount}, Resolution: ${this._resolution}`);
268 }
269
270 stop(): void {
271 this._status = "inactive";
272 console.log(`Stopped monitoring system ${this._id} in location ${this._location}`);
273 }
274
275 performReview(): void {
276 this._status = "maintenance";
277 console.log(`Started monitoring system review ${this._id}`);
278
279 // In this case, the inspection completes immediately
280 this._reviewDate = new Date();
281 this._status = "active";
282 console.log(`Completed monitoring system review ${this._id}`);
283 }
284
285 // Methods specific to this system type
286 enableNightMode(): void {
287 this._nightMode = true;
288 console.log(`Enabled night mode in monitoring system ${this._id}`);
289 }
290
291 disableNightMode(): void {
292 this._nightMode = false;
293 console.log(`Disabled night mode in monitoring system ${this._id}`);
294 }
295
296 detectMotion(area: string): void {
297 console.log(`System ${this._id}: Motion detected in area ${area}`);
298
299 if (SecuritySystem.alertLevel !== "normall") {
300 SecuritySystem.reportIncident(
301 this.systemType,
302 `${this._location} - area ${area}`,
303 `Detected unauthorized motion!`
304 );
305 }
306 }
307}
308
309// Demonstration of Jurassic Park security system
310function testSecuritySystem() {
311 console.log("=== INITIALIZING JURASSIC PARK SECURITY SYSTEMS ===");
312
313 // Creating fence systems
314 const mainFence = new ElectricFenceSystem("FNC-001", "Sector A - Main entrance", 2500, "ultra-reinforced");
315 const tRexFence = new ElectricFenceSystem("FNC-002", "Sector B - Enclosure T-Rex", 1200, "ultra-reinforced");
316 const raptorFence = new ElectricFenceSystem("FNC-003", "Sector C - Raptor Enclosure", 800, "reinforced");
317
318 // Creating monitoring systems
319 const mainMonitoring = new VideoMonitoringSystem("CAM-001", "Control center", 20, "4K");
320 const monitoringTRex = new VideoMonitoringSystem("CAM-002", "Sector B - Enclosure T-Rex", 12, "HD");
321 const raptorMonitoring = new VideoMonitoringSystem("CAM-003", "Sector C - Raptor Enclosure", 8, "HD");
322
323 // Starting all systems
324 console.log("\n=== STARTING SYSTEMS ===");
325 mainFence.start();
326 tRexFence.start();
327 raptorFence.start();
328
329 mainMonitoring.start();
330 monitoringTRex.start();
331 raptorMonitoring.start();
332
333 // Starting monitoring
334 console.log("\n=== ACTIVATION MONITORING ===");
335 mainFence.startMonitoring();
336 tRexFence.startMonitoring();
337 raptorFence.startMonitoring();
338
339 mainMonitoring.startMonitoring();
340 monitoringTRex.startMonitoring();
341 raptorMonitoring.startMonitoring();
342
343 // Checking statuses
344 console.log("\n=== SYSTEM REPORTS ===");
345 console.log(mainFence.generateStatusReport());
346 console.log(monitoringTRex.generateStatusReport());
347
348 // Simulating an incident
349 console.log("\n=== SIMULATION INCIDENT ===");
350 console.log("Current alert level:", SecuritySystem.alertLevel);
351
352 raptorMonitoring.detectMotion("South corner");
353 raptorFence.reportFenceBreach();
354
355 // Checking alert level after incidents
356 console.log("\nCurrent alert level after incidents:", SecuritySystem.alertLevel);
357
358 // Displaying recent incidents
359 console.log("\n=== LAST INCIDENTS ===");
360 SecuritySystem.recentIncidents.forEach((incident, index) => {
361 console.log(`${index + 1}. [${incident.date.toLocaleString()}] ${incident.systemType} in ${incident.location}: ${incident.description}`);
362 });
363
364 // Conducting inspection
365 console.log("\n=== SYSTEM REVIEW ===");
366 tRexFence.performReview();
367 mainMonitoring.performReview();
368
369 // Manual alert level change
370 console.log("\n=== NORMALIZATION SITUATION ===");
371 SecuritySystem.changeAlertLevel("normall");
372}
373
374// Running the test
375testSecuritySystem();Dans cet exemple avancé :
- Nous utilisons la classe abstraite
SecuritySystemcomme base de tous les systèmes de sécurité - La classe définit des propriétés et méthodes statiques (par ex. le niveau d'alerte du parc, la liste des incidents)
- Les méthodes et propriétés abstraites imposent l'implémentation de fonctionnalités spécifiques dans les classes dérivées
- Les classes dérivées (
ElectricFenceSystemetVideoMonitoringSystem) implémentent les éléments abstraits - Les éléments statiques permettent une coordination centrale (par ex. signaler des incidents et gérer les niveaux d'alerte)
Combiner éléments statiques et abstraits
Les éléments statiques et abstraits peuvent être un outil puissant lorsqu'on les utilise ensemble dans une hiérarchie de classes bien conçue. Voici quelques motifs et exemples de leur application :
1. Classe de base abstraite avec méthodes fabriques statiques
1abstract class DinosaurSpecies {
2 // Static properties
3 private static _registeredDinosaurs: Map<string, DinosaurSpecies> = new Map();
4
5 // Instance properties
6 private _name: string;
7 private _eraValue: string;
8 protected _dietType: "carnivore" | "herbivorous" | "omnivore";
9
10 constructor(name: string, era: string, diet: "carnivore" | "herbivorous" | "omnivore") {
11 this._name = name;
12 this._eraValue = era;
13 this._dietType = diet;
14
15 // Adding to registry
16 DinosaurSpecies._registeredDinosaurs.set(name, this);
17 }
18
19 // Static factory method - creates the appropriate species based on diet
20 static createSpecies(
21 name: string,
22 era: string,
23 diet: "carnivore" | "herbivorous" | "omnivore",
24 additionalParams: Record<string, any>
25 ): DinosaurSpecies {
26 if (diet === "carnivore") {
27 return new CarnivoreSpecies(
28 name,
29 era,
30 additionalParams.biteForce,
31 additionalParams.runSpeed
32 );
33 } else {
34 return new HerbivoreSpecies(
35 name,
36 era,
37 additionalParams.neckLength || 0,
38 additionalParams.hasArmor || false
39 );
40 }
41 }
42
43 // Static method for retrieving registered species
44 static getSpecies(name: string): DinosaurSpecies | undefined {
45 return this._registeredDinosaurs.get(name);
46 }
47
48 static get allSpecies(): string[] {
49 return Array.from(this._registeredDinosaurs.keys());
50 }
51
52 // Instance getters
53 get name(): string { return this._name; }
54 get era(): string { return this._eraValue; }
55 get dietType(): string { return this._dietType; }
56
57 // Abstract methods
58 abstract behaviorDescription(): string;
59 abstract estimateWeight(length: number): number;
60
61 // Method using abstract methods
62 generateDescription(): string {
63 return `${this._name} - dinosaur ${this._dietType} from the ${this._eraValue} era.
64${this.behaviorDescription()}`;
65 }
66}
67
68// Derived classes
69class CarnivoreSpecies extends DinosaurSpecies {
70 private _biteForce: number; // in PSI
71 private _runSpeed: number; // in km/h
72
73 constructor(name: string, era: string, biteForce: number, runSpeed: number) {
74 super(name, era, "carnivore");
75 this._biteForce = biteForce;
76 this._runSpeed = runSpeed;
77 }
78
79 // Getters
80 get biteForce(): number { return this._biteForce; }
81 get runSpeed(): number { return this._runSpeed; }
82
83 // Implementation of abstract methods
84 behaviorDescription(): string {
85 return `Hunts at speed ${this._runSpeed} km/h and bite force ${this._biteForce} PSI.`;
86 }
87
88 estimateWeight(length: number): number {
89 // Approximate formula for predators
90 return length * length * 100;
91 }
92}
93
94class HerbivoreSpecies extends DinosaurSpecies {
95 private _neckLength: number; // in meters
96 private _hasArmor: boolean;
97
98 constructor(name: string, era: string, neckLength: number, hasArmor: boolean) {
99 super(name, era, "herbivorous");
100 this._neckLength = neckLength;
101 this._hasArmor = hasArmor;
102 }
103
104 // Getters
105 get neckLength(): number { return this._neckLength; }
106 get hasArmor(): boolean { return this._hasArmor; }
107
108 // Implementation of abstract methods
109 behaviorDescription(): string {
110 const neckDescription = this._neckLength > 3
111 ? `Has a long neck (${this._neckLength}m) allowing it to eat tall vegetation.`
112 : "Feeds on low vegetation.";
113
114 const armorDescription = this._hasArmor
115 ? "Has armor for protection against predators."
116 : "Does not have protective armor.";
117
118 return `${neckDescription} ${armorDescription}`;
119 }
120
121 estimateWeight(length: number): number {
122 // Approximate formula for herbivores (usually heavier)
123 let weight = length * length * 150;
124
125 // Additional weight for armor
126 if (this._hasArmor) {
127 weight *= 1.3;
128 }
129
130 return weight;
131 }
132}
133
134// Usage
135function testDinosaurSpecies() {
136 // Creating species using the factory method
137 DinosaurSpecies.createSpecies(
138 "Tyrannosaurus Rex",
139 "late Cretaceous",
140 "carnivore",
141 { biteForce: 12800, runSpeed: 32 }
142 );
143
144 DinosaurSpecies.createSpecies(
145 "Velociraptor",
146 "late Cretaceous",
147 "carnivore",
148 { biteForce: 2000, runSpeed: 64 }
149 );
150
151 DinosaurSpecies.createSpecies(
152 "Brachiosaurus",
153 "late Jurassic",
154 "herbivorous",
155 { neckLength: 9, hasArmor: false }
156 );
157
158 DinosaurSpecies.createSpecies(
159 "Stegosaurus",
160 "late Jurassic",
161 "herbivorous",
162 { neckLength: 0.5, hasArmor: true }
163 );
164
165 // Accessing registered species
166 console.log("Available species:", DinosaurSpecies.allSpecies);
167
168 // Retrieving species information
169 const trex = DinosaurSpecies.getSpecies("Tyrannosaurus Rex");
170 const brach = DinosaurSpecies.getSpecies("Brachiosaurus");
171
172 if (trex) {
173 console.log(trex.generateDescription());
174 console.log(`Estimated weight (12m long): ${trex.estimateWeight(12)} kg`);
175 }
176
177 if (brach) {
178 console.log(brach.generateDescription());
179 console.log(`Estimated weight (25m long): ${brach.estimateWeight(25)} kg`);
180 }
181
182 // Type checking
183 const steg = DinosaurSpecies.getSpecies("Stegosaurus") as HerbivoreSpecies;
184 if (steg && steg instanceof HerbivoreSpecies) {
185 console.log(`Stegosaurus has armor: ${steg.hasArmor ? "YES" : "NO"}`);
186 }
187
188 const raptor = DinosaurSpecies.getSpecies("Velociraptor") as CarnivoreSpecies;
189 if (raptor && raptor instanceof CarnivoreSpecies) {
190 console.log(`Velociraptor - run speed: ${raptor.runSpeed} km/h`);
191 }
192}
193
194testDinosaurSpecies();2. Méthodes statiques abstraites
Depuis la version 4.0 de TypeScript, il est possible de définir des méthodes statiques abstraites. Cela permet de créer des hiérarchies de classes où les classes dérivées doivent implémenter des méthodes statiques précises :
1abstract class LocationSystem {
2 // Instance properties
3 protected _id: string;
4 protected _name: string;
5
6 constructor(id: string, name: string) {
7 this._id = id;
8 this._name = name;
9 }
10
11 // Abstract static method that derived classes must implement
12 static abstract generateLocationId(type: string, numer: number): string;
13
14 // Regular abstract instance method
15 abstract getLocationInfo(): Record<string, any>;
16
17 // Instance method
18 generateReport(): string {
19 const info = this.getLocationInfo();
20
21 let report = `=== REPORT LOCATION: ${this._name} (ID: ${this._id}) ===\n`;
22
23 for (const [key, value] of Object.entries(info)) {
24 report += `${key}: ${value}\n`;
25 }
26
27 return report;
28 }
29}
30
31class DinosaurEnclosure extends LocationSystem {
32 private _species: string;
33 private _area: number; // in hectares
34 private _specimenCount: number;
35
36 constructor(numer: number, name: string, species: string, area: number, specimenCount: number) {
37 // Generating ID for the enclosure
38 const id = DinosaurEnclosure.generateLocationId("enclosure", numer);
39 super(id, name);
40
41 this._species = species;
42 this._area = area;
43 this._specimenCount = specimenCount;
44 }
45
46 // Implementation of abstract static method
47 static generateLocationId(type: string, numer: number): string {
48 return `${type.toUpperCase()}-${numer.toString().padStart(3, '0')}`;
49 }
50
51 // Implementation of abstract instance method
52 getLocationInfo(): Record<string, any> {
53 return {
54 type: "Dinosaur enclosure",
55 species: this._species,
56 area: `${this._area} ha`,
57 specimenCount: this._specimenCount,
58 populationDensity: `${(this._specimenCount / this._area).toFixed(2)} spec./ha`
59 };
60 }
61
62 // Methods specific to this class
63 addSpecimen(): void {
64 this._specimenCount++;
65 console.log(`Added new specimen to enclosure ${this._name}. New count: ${this._specimenCount}`);
66 }
67
68 removeSpecimen(): boolean {
69 if (this._specimenCount > 0) {
70 this._specimenCount--;
71 console.log(`Removed specimen from enclosure ${this._name}. New count: ${this._specimenCount}`);
72 return true;
73 }
74
75 console.log(`Error: Cannot remove specimen from enclosure ${this._name} - no specimens.`);
76 return false;
77 }
78}
79
80class TouristAttraction extends LocationSystem {
81 private _attractionType: string;
82 private _capacity: number;
83 private _waitingTime: number; // in minutes
84
85 constructor(numer: number, name: string, attractionType: string, capacity: number) {
86 // Generating ID for the attraction
87 const id = TouristAttraction.generateLocationId("attraction", numer);
88 super(id, name);
89
90 this._attractionType = attractionType;
91 this._capacity = capacity;
92 this._waitingTime = 0;
93 }
94
95 // Implementation of abstract static method
96 static generateLocationId(type: string, numer: number): string {
97 // Different ID format for attractions
98 return `${type.substring(0, 3).toUpperCase()}${numer.toString().padStart(4, '0')}`;
99 }
100
101 // Implementation of abstract instance method
102 getLocationInfo(): Record<string, any> {
103 return {
104 type: "Tourist attraction",
105 category: this._attractionType,
106 capacity: this._capacity,
107 waitingTime: `${this._waitingTime} min`
108 };
109 }
110
111 // Methods specific to this class
112 updateWaitingTime(newWaitingTime: number): void {
113 this._waitingTime = newWaitingTime;
114 console.log(`Updated waiting time for ${this._name}: ${this._waitingTime} min`);
115 }
116}
117
118// Application
119function testLocationSystem() {
120 // Creating enclosures
121 const tRexEnclosure = new DinosaurEnclosure(1, "T-Rex Kingdom", "Tyrannosaurus Rex", 5, 1);
122 const raptorEnclosure = new DinosaurEnclosure(2, "Raptor Valley", "Velociraptor", 3, 6);
123
124 // Creating attractions
125 const visitorCenter = new TouristAttraction(101, "Genetics Center", "educational", 500);
126 const jeepSafari = new TouristAttraction(102, "Jurassic Safari", "ride", 50);
127
128 // Displaying reports
129 console.log(tRexEnclosure.generateReport());
130 console.log(raptorEnclosure.generateReport());
131 console.log(visitorCenter.generateReport());
132 console.log(jeepSafari.generateReport());
133
134 // Performing operations
135 raptorEnclosure.addSpecimen();
136 raptorEnclosure.addSpecimen();
137 jeepSafari.updateWaitingTime(45);
138
139 // Checking updated data
140 console.log(raptorEnclosure.generateReport());
141 console.log(jeepSafari.generateReport());
142
143 // Demonstration of different ID formats generated by static method implementations
144 console.log("Enclosure ID format:", DinosaurEnclosure.generateLocationId("enclosure", 42));
145 console.log("Attraction ID format:", TouristAttraction.generateLocationId("attraction", 42));
146}
147
148testLocationSystem();Résumé
Dans cet exercice, nous avons découvert deux mécanismes puissants de TypeScript :
Propriétés et méthodes statiques :
- Appartiennent à la classe, et non aux instances
- Permettent de partager des données et des fonctionnalités entre toutes les instances
- Idéales pour définir des constantes, un état global et des méthodes utilitaires
- Accessibles directement via le nom de la classe, sans créer d'instance
Classes et méthodes abstraites :
- Définissent un « modèle » ou un « contrat » qui doit être implémenté par les classes qui en héritent
- Ne peuvent pas être instanciées directement
- Imposent l'implémentation de méthodes précises dans les classes dérivées
- Permettent de partager des fonctionnalités communes au sein d'une hiérarchie de classes
En combinant ces concepts, nous pouvons créer des systèmes élégants, modulaires et sûrs en matière de types, parfaits pour modéliser des relations et processus complexes, comme ceux du système de gestion de Jurassic Park.
Le Dr Henry Wu le résumerait ainsi : « Dans la nature comme en programmation, il y a de la beauté dans la structure et l'ordre. Les éléments statiques assurent la cohérence de toute l'espèce, tandis que les concepts abstraits forment le plan de base que chaque organisme doit implémenter à sa manière. Ensemble, ils créent une harmonie parfaite - avec les protections adéquates, bien sûr... dans la plupart des cas. »
Code de cette leçon : index.ts
1// Static properties et abstract classes - Parc Jurassique
2console.log("=== STATIC PROPERTIES AND ABSTRACT CLASSES ===\n");
3
4// ===========================================
5// 1. Classe abstraite Dinosaur
6// ===========================================
7
8abstract class Dinosaur {
9 // Compteur statique pour tous les dinosaures
10 static totalCount: number = 0;
11 static readonly PARK_NAME: string = "Jurassic World";
12
13 // Méthode fabrique statique
14 static createId(): string {
15 return `DINO-${String(++Dinosaur.totalCount).padStart(4, "0")}`;
16 }
17
18 // Champs d'instance
19 readonly id: string;
20 name: string;
21 protected weight: number;
22
23 constructor(name: string, weight: number) {
24 this.id = Dinosaur.createId();
25 this.name = name;
26 this.weight = weight;
27 }
28
29 // Méthode abstraite - chaque espèce doit l'implémenter
30 abstract makeSound(): string;
31 abstract getDangerLevel(): number;
32
33 // Méthode commune
34 getProfile(): string {
35 return `[${this.id}] ${this.name} - ${this.weight}kg`;
36 }
37}
38
39// ===========================================
40// 2. Implémentations des classes concrètes
41// ===========================================
42
43class TRex extends Dinosaur {
44 private biteForce: number;
45
46 constructor(name: string, weight: number, biteForce: number) {
47 super(name, weight);
48 this.biteForce = biteForce;
49 }
50
51 makeSound(): string {
52 return "ROOOOAAAR!";
53 }
54
55 getDangerLevel(): number {
56 return 10;
57 }
58}
59
60class Triceratops extends Dinosaur {
61 private hornLength: number;
62
63 constructor(name: string, weight: number, hornLength: number) {
64 super(name, weight);
65 this.hornLength = hornLength;
66 }
67
68 makeSound(): string {
69 return "BRRRRR!";
70 }
71
72 getDangerLevel(): number {
73 return 4;
74 }
75}
76
77// ===========================================
78// 3. Méthode fabrique statique
79// ===========================================
80
81class DinosaurFactory {
82 static registry: Map<string, Dinosaur> = new Map();
83
84 static create(type: "trex" | "triceratops", name: string): Dinosaur {
85 let dino: Dinosaur;
86 if (type === "trex") {
87 dino = new TRex(name, 8000, 12800);
88 } else {
89 dino = new Triceratops(name, 6000, 90);
90 }
91 DinosaurFactory.registry.set(dino.id, dino);
92 return dino;
93 }
94
95 static getAll(): Dinosaur[] {
96 return Array.from(DinosaurFactory.registry.values());
97 }
98}
99
100// ===========================================
101// Test
102// ===========================================
103
104const rex = DinosaurFactory.create("trex", "Rexy");
105const trike = DinosaurFactory.create("triceratops", "Cera");
106const rex2 = DinosaurFactory.create("trex", "Buck");
107
108console.log("Park:", Dinosaur.PARK_NAME);
109console.log("Nombre de dinosaures:", Dinosaur.totalCount);
110console.log("");
111
112DinosaurFactory.getAll().forEach(dino => {
113 console.log(dino.getProfile());
114 console.log(" Son:", dino.makeSound());
115 console.log(" Danger:", dino.getDangerLevel(), "/10");
116});
117
118// On ne peut pas créer d'instance d'une classe abstraite :
119// const dino = new Dinosaur("Test", 100); // Error!
120console.log("\nUne classe abstraite ne peut pas être instanciée directement.");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. Qu'est-ce qu'une classe abstraite en TypeScript ?
2. En quoi les propriétés statiques diffèrent-elles des propriétés ordinaires dans une classe ?
Tâches pratiques dans le jeu
- Éditeur de code
Implémente des classes avec des propriétés statiques et abstraites.
- Clique dans l'ordre
Range les éléments de la déclaration d'une classe abstraite :
- Éditeur de code
Crée des classes avec différents modificateurs d'accès pour le système du parc