Cours JavaScript et TypeScript · Module 7: TypeScript avancé
Décorateurs de classe
Dans cette leçon9
« À Jurassic Park, chaque bâtiment et chaque infrastructure doivent être correctement étiquetés et sécurisés », explique John Hammond en traversant le Visitor Center fraîchement construit. « Certaines zones sont accessibles à tous, d'autres uniquement au personnel disposant des habilitations appropriées, et d'autres encore nécessitent des protocoles de sécurité spéciaux. Sans un bon système d'étiquettes et de protections, ce serait le chaos. »
En TypeScript, comme à Jurassic Park, nous avons besoin d'un moyen d'étiqueter et de modifier des classes sans toucher à leur implémentation interne. Les décorateurs de classe sont comme des étiquettes spéciales et des protocoles de sécurité que l'on peut appliquer aux classes pour leur ajouter des fonctionnalités ou des caractéristiques supplémentaires.
Introduction aux décorateurs de classe
Les décorateurs de classe permettent de modifier ou d'étendre des classes de manière déclarative. Ils sont particulièrement utiles lorsqu'on veut :
- Ajouter des métadonnées à une classe
- Enregistrer automatiquement une classe dans un registre
- Ajouter des méthodes ou des propriétés à une classe
- Modifier le comportement du constructeur
- Appliquer des patrons de conception comme Singleton ou Factory
Un décorateur de classe est simplement une fonction qui prend le constructeur d'une classe comme argument et peut le modifier, l'étendre ou le remplacer entièrement.
Syntaxe de base d'un décorateur de classe
Les décorateurs de classe se placent au-dessus de la définition de la classe, précédés du symbole @ :
1@decorator
2class MyClass {
3 // class implementation
4}Où decorator est une fonction :
1function decorator(target: Function) {
2 // Modification or extension of the class
3}Exemple : système de surveillance à Jurassic Park
Commençons par un décorateur de classe simple qui marquera les objets nécessitant une surveillance à Jurassic Park :
1// Class decorator
2function Monitored(target: Function) {
3 // Add a property to the class prototype
4 target.prototype.monitored = true;
5
6 // Add a method to the class prototype
7 target.prototype.startMonitoring = function() {
8 console.log(`Monitoring started: ${this.name || this.constructor.name}`);
9 };
10}
11
12// Using the decorator
13@Monitored
14class DinosaurEnclosure {
15 name: string;
16
17 constructor(name: string) {
18 this.name = name;
19 }
20
21 addDinosaur(dinosaur: string) {
22 console.log(`Added ${dinosaur} to enclosure ${this.name}`);
23 }
24}
25
26// Using the class with the decorator
27const raptorEnclosure = new DinosaurEnclosure("Velociraptor");
28raptorEnclosure.addDinosaur("Blue");
29
30// Now we can use the properties and methods added by the decorator
31if ((raptorEnclosure as any).monitored) {
32 (raptorEnclosure as any).startMonitoring();
33}Dans cet exemple, le décorateur Monitored ajoute une propriété monitored et une méthode startMonitoring au prototype de la classe DinosaurEnclosure. Ainsi, toutes les instances de cette classe auront accès à ces fonctionnalités supplémentaires.
Décorateurs de classe avec paramètres
On peut aussi créer des décorateurs qui acceptent des paramètres. Dans ce cas, notre fonction décoratrice doit renvoyer une autre fonction, qui sera le décorateur proprement dit :
1// Decorator factory
2function SecurityLevel(level: number) {
3 // We return the actual class decorator
4 return function(target: Function) {
5 // Add a property to the class (not to the prototype)
6 target.prototype.securityLevel = level;
7
8 // Add an access verification method
9 target.prototype.checkAccess = function(employeeLevel: number): boolean {
10 return employeeLevel >= level;
11 };
12
13 // We can also override the constructor
14 const originalConstructor = target;
15
16 // Function replacing the constructor
17 function newConstructor(...args: any[]) {
18 console.log(`Creating object with security level: ${level}`);
19
20 // Call the original constructor
21 const instance = new (originalConstructor as any)(...args);
22
23 // Additional logic can be added
24 if (level >= 4) {
25 console.log("WARNING: Creating high security level object!");
26 }
27
28 return instance;
29 }
30
31 // Copy prototype properties
32 newConstructor.prototype = originalConstructor.prototype;
33
34 // Return the new constructor
35 return newConstructor as any;
36 };
37}
38
39// Using the decorator with a parameter
40@SecurityLevel(4)
41class GeneticLaboratory {
42 name: string;
43
44 constructor(name: string) {
45 this.name = name;
46 console.log(`Laboratory created: ${name}`);
47 }
48
49 cloneDinosaur(species: string) {
50 console.log(`Cloning ${species} in laboratory ${this.name}`);
51 }
52}
53
54// Using the class with the decorator
55const laboratory = new GeneticLaboratory("Hammond Lab");
56
57// Checking the security level
58console.log(`Security level: ${(laboratory as any).securityLevel}`);
59
60// Verifying access
61const employeeLevel = 3;
62if ((laboratory as any).checkAccess(employeeLevel)) {
63 console.log("Access granted to laboratory");
64 laboratory.cloneDinosaur("Velociraptor");
65} else {
66 console.log("Access denied! Higher security level required.");
67}Dans cet exemple, le décorateur SecurityLevel prend un paramètre précisant le niveau d'accès requis. Il ajoute une propriété securityLevel et une méthode checkAccess à la classe, et redéfinit aussi le constructeur pour ajouter une journalisation supplémentaire.
Combiner plusieurs décorateurs
On peut appliquer plusieurs décorateurs à une même classe. Dans ce cas, les décorateurs sont appliqués de bas en haut (du dernier au premier) :
1// Decorator that registers in the system
2function Registered(target: Function) {
3 console.log(`Registering class: ${target.name}`);
4 (target as any).registered = true;
5}
6
7// Decorator that adds logging
8function Logged(prefix: string) {
9 return function(target: Function) {
10 // Save original methods
11 const originalMethods: Record<string, Function> = {};
12
13 // Iterate over prototype methods
14 for (const propertyName of Object.getOwnPropertyNames(target.prototype)) {
15 const descriptor = Object.getOwnPropertyDescriptor(target.prototype, propertyName);
16
17 // Check if it's a method and not a constructor
18 if (descriptor && typeof descriptor.value === 'function' && propertyName !== 'constructor') {
19 originalMethods[propertyName] = descriptor.value;
20
21 // Override the method, adding logging
22 Object.defineProperty(target.prototype, propertyName, {
23 value: function(...args: any[]) {
24 console.log(`${prefix}: Calling ${propertyName} with arguments: ${JSON.stringify(args)}`);
25 const result = originalMethods[propertyName].apply(this, args);
26 console.log(`${prefix}: ${propertyName} returned: ${JSON.stringify(result)}`);
27 return result;
28 }
29 });
30 }
31 }
32 };
33}
34
35// Using multiple decorators
36@Registered
37@SecurityLevel(3)
38@Logged("[Gate control system]")
39class GateControlSystem {
40 state: "open" | "closed" = "closed";
41
42 constructor() {
43 console.log("Initializing gate control system.");
44 }
45
46 openGates(): boolean {
47 console.log("Opening park gates...");
48 this.state = "open";
49 return true;
50 }
51
52 closeGates(): boolean {
53 console.log("Closing park gates...");
54 this.state = "closed";
55 return true;
56 }
57
58 checkStatus(): string {
59 return `Gates are ${this.state}`;
60 }
61}
62
63// Using the class with multiple decorators
64const gateControl = new GateControlSystem();
65gateControl.openGates();
66console.log(gateControl.checkStatus());
67gateControl.closeGates();Dans cet exemple, la classe GateControlSystem a trois décorateurs appliqués :
@Registered- enregistre la classe dans le système@SecurityLevel(3)- définit le niveau de sécurité à 3@Logged("[Gate control system]")- ajoute une journalisation à toutes les méthodes de la classe
Exemple pratique : système de gestion du parc
Voici un exemple plus élaboré d'un système de gestion de Jurassic Park utilisant divers décorateurs de classe :
1// Basic interface for all park resources
2interface ParkResource {
3 id: string;
4 name: string;
5 location: string;
6 status: "active" | "inactive" | "maintenance" | "failure";
7}
8
9// Park resource registry
10class ResourceRegistry {
11 private static registry: Map<string, ParkResource[]> = new Map();
12
13 static registerResource(type: string, resource: ParkResource): void {
14 if (!this.registry.has(type)) {
15 this.registry.set(type, []);
16 }
17
18 this.registry.get(type)!.push(resource);
19 console.log(`Resource registered: ${resource.name} (${type})`);
20 }
21
22 static getResources(type: string): ParkResource[] {
23 return this.registry.get(type) || [];
24 }
25
26 static getAllResources(): Map<string, ParkResource[]> {
27 return new Map(this.registry);
28 }
29}
30
31// Decorator for resource registration
32function RegisterResource(type: string) {
33 return function<T extends { new(...args: any[]): ParkResource }>(constructor: T) {
34 return class extends constructor {
35 constructor(...args: any[]) {
36 super(...args);
37 ResourceRegistry.registerResource(type, this);
38 }
39 };
40 };
41}
42
43// Decorator enforcing audits
44function RequiresAudit(frequencyDays: number) {
45 return function(target: Function) {
46 target.prototype.lastAudit = new Date();
47 target.prototype.auditFrequency = frequencyDays;
48
49 target.prototype.requiresAudit = function(): boolean {
50 const now = new Date();
51 const timeDifference = now.getTime() - this.lastAudit.getTime();
52 const differenceDays = timeDifference / (1000 * 3600 * 24);
53
54 return differenceDays >= this.auditFrequency;
55 };
56
57 target.prototype.performAudit = function(): void {
58 console.log(`Audit conducted for: ${this.name}`);
59 this.lastAudit = new Date();
60 };
61 };
62}
63
64// Decorator adding status history tracking
65function TrackStatuses(target: Function) {
66 // Add status history array
67 target.prototype.statusHistory = [];
68
69 // Save original status setter
70 const originalSetStatus = Object.getOwnPropertyDescriptor(
71 target.prototype, 'status'
72 )?.set;
73
74 // Override the setter for status
75 if (originalSetStatus) {
76 Object.defineProperty(target.prototype, 'status', {
77 set: function(newStatus: "active" | "inactive" | "maintenance" | "failure") {
78 const oldTime = new Date();
79 const oldStatus = this._status;
80
81 // Call the original setter
82 originalSetStatus.call(this, newStatus);
83
84 // Save the change in history
85 this.statusHistory.push({
86 changeTime: oldTime,
87 from: oldStatus,
88 to: newStatus
89 });
90
91 console.log(`Status changed for ${this.name}: ${oldStatus || 'none'} -> ${newStatus}`);
92 },
93 get: Object.getOwnPropertyDescriptor(target.prototype, 'status')?.get
94 });
95 }
96}
97
98// Singleton decorator
99function Singleton<T extends { new(...args: any[]): any }>(constructor: T) {
100 // Save original constructor
101 const originalConstructor = constructor;
102
103 // Replace constructor with a function that controls instance creation
104 const newConstructor: any = function(...args: any[]) {
105 if (!newConstructor.instance) {
106 newConstructor.instance = new originalConstructor(...args);
107 } else {
108 console.warn(`Singleton ${constructor.name} already exists. Returning existing instance.`);
109 }
110
111 return newConstructor.instance;
112 };
113
114 // Copy prototype
115 newConstructor.prototype = originalConstructor.prototype;
116
117 // Store original constructor
118 newConstructor.originalConstructor = originalConstructor;
119
120 return newConstructor;
121}
122
123// Implementation of various park resources
124
125@RegisterResource("enclosure")
126@RequiresAudit(7) // Audit every 7 days
127@TrackStatuses
128class DinosaursEnclosure implements ParkResource {
129 id: string;
130 name: string;
131 location: string;
132 private _status: "active" | "inactive" | "maintenance" | "failure" = "active";
133
134 // Enclosure-specific properties
135 genusDinosaurs: string;
136 capacity: number;
137 currentCount: number;
138
139 constructor(
140 id: string,
141 name: string,
142 location: string,
143 genusDinosaurs: string,
144 capacity: number
145 ) {
146 this.id = id;
147 this.name = name;
148 this.location = location;
149 this.genusDinosaurs = genusDinosaurs;
150 this.capacity = capacity;
151 this.currentCount = 0;
152 }
153
154 get status(): "active" | "inactive" | "maintenance" | "failure" {
155 return this._status;
156 }
157
158 set status(newStatus: "active" | "inactive" | "maintenance" | "failure") {
159 this._status = newStatus;
160 }
161
162 addDinosaur(): boolean {
163 if (this.currentCount < this.capacity && this.status === "active") {
164 this.currentCount++;
165 console.log(`Dinosaur added to enclosure ${this.name}. Current count: ${this.currentCount}`);
166 return true;
167 } else {
168 console.log(`Cannot add dinosaur to enclosure ${this.name}. Enclosure full or inactive.`);
169 return false;
170 }
171 }
172
173 removeDinosaur(): boolean {
174 if (this.currentCount > 0) {
175 this.currentCount--;
176 console.log(`Dinosaur removed from enclosure ${this.name}. Current count: ${this.currentCount}`);
177 return true;
178 } else {
179 console.log(`Cannot remove dinosaur from enclosure ${this.name}. Enclosure empty.`);
180 return false;
181 }
182 }
183}
184
185@RegisterResource("building")
186@RequiresAudit(30) // Audit every 30 days
187@TrackStatuses
188class ParkBuilding implements ParkResource {
189 id: string;
190 name: string;
191 location: string;
192 private _status: "active" | "inactive" | "maintenance" | "failure" = "active";
193
194 // Building-specific properties
195 genusBuilding: "public" | "administration" | "laboratory" | "maintenance";
196 area: number;
197
198 constructor(
199 id: string,
200 name: string,
201 location: string,
202 genusBuilding: "public" | "administration" | "laboratory" | "maintenance",
203 area: number
204 ) {
205 this.id = id;
206 this.name = name;
207 this.location = location;
208 this.genusBuilding = genusBuilding;
209 this.area = area;
210 }
211
212 get status(): "active" | "inactive" | "maintenance" | "failure" {
213 return this._status;
214 }
215
216 set status(newStatus: "active" | "inactive" | "maintenance" | "failure") {
217 this._status = newStatus;
218 }
219
220 open(): void {
221 if (this.status !== "active") {
222 console.log(`Cannot open building ${this.name}. Status: ${this.status}`);
223 return;
224 }
225
226 console.log(`Building ${this.name} opened`);
227 }
228
229 close(): void {
230 console.log(`Building ${this.name} closed`);
231 }
232}
233
234@Singleton
235@RegisterResource("system")
236class SecuritySystem implements ParkResource {
237 id: string;
238 name: string;
239 location: string;
240 private _status: "active" | "inactive" | "maintenance" | "failure" = "active";
241
242 // Security system-specific properties
243 emergencyProtocol: "normall" | "warning" | "threat" | "evacuation" = "normall";
244
245 constructor() {
246 this.id = "SYS-SEC-001";
247 this.name = "Main Security System";
248 this.location = "Control center";
249 }
250
251 get status(): "active" | "inactive" | "maintenance" | "failure" {
252 return this._status;
253 }
254
255 set status(newStatus: "active" | "inactive" | "maintenance" | "failure") {
256 this._status = newStatus;
257
258 if (newStatus !== "active" && this.emergencyProtocol === "normall") {
259 console.warn("WARNING: Security system is not active!");
260 this.setProtocol("warning");
261 }
262 }
263
264 setProtocol(protocol: "normall" | "warning" | "threat" | "evacuation"): void {
265 this.emergencyProtocol = protocol;
266 console.log(`Security protocol set to: ${protocol}`);
267
268 // Action implementation depending on the protocol
269 switch (protocol) {
270 case "normall":
271 console.log("All systems operating normallly.");
272 break;
273 case "warning":
274 console.log("Increased staff alertness. Checking all systems.");
275 break;
276 case "threat":
277 console.log("WARNING: Threat detected! Staff to positions! Prepare for evacuation.");
278 break;
279 case "evacuation":
280 console.log("WARNING! WARNING! Immediate park evacuation! This is not a drill!");
281 break;
282 }
283 }
284
285 checkStatus(): void {
286 console.log(`Security system status: ${this.status}`);
287 console.log(`Current protocol: ${this.emergencyProtocol}`);
288 }
289}
290
291// Using classes with decorators
292
293// Creating an enclosure
294const enclosureT = new DinosaursEnclosure(
295 "ZAG-001",
296 "T-Rex Enclosure",
297 "Sector A",
298 "Tyrannosaurus Rex",
299 2
300);
301
302enclosureT.addDinosaur();
303enclosureT.status = "maintenance";
304enclosureT.status = "active";
305
306// Creating a building
307const visitorCenter = new ParkBuilding(
308 "BUD-001",
309 "Visitor Center",
310 "Main entrance",
311 "public",
312 2500
313);
314
315visitorCenter.open();
316visitorCenter.status = "maintenance";
317visitorCenter.status = "active";
318
319// Creating the security system (Singleton)
320const securitySystem1 = new SecuritySystem();
321securitySystem1.checkStatus();
322securitySystem1.setProtocol("warning");
323
324// Attempting to create a second security system
325const securitySystem2 = new SecuritySystem();
326// Note that securitySystem1 === securitySystem2
327
328// Checking audits
329console.log(`T-Rex Enclosure requires audit: ${(enclosureT as any).requiresAudit()}`);
330console.log(`Visitor Center requires audit: ${(visitorCenter as any).requiresAudit()}`);
331
332// Conducting audit
333if ((enclosureT as any).requiresAudit()) {
334 (enclosureT as any).performAudit();
335}
336
337// Getting all resources from registry
338console.log("Park resource list:");
339const allResources = ResourceRegistry.getAllResources();
340for (const [type, resources] of allResources) {
341 console.log(`Type: ${type}, Count: ${resources.length}`);
342 resources.forEach(resource => {
343 console.log(` - ${resource.name} (${resource.status})`);
344 });
345}
346
347// Checking status history
348console.log("T-Rex Enclosure status history:");
349(enclosureT as any).statusHistory.forEach((change: any) => {
350 console.log(` ${change.changeTime.toLocaleString()}: ${change.from || 'initial'} -> ${change.to}`);
351});Cet exemple élaboré illustre une utilisation avancée des décorateurs de classe :
@RegisterResource- enregistre automatiquement les ressources dans un registre central@RequiresAudit- ajoute une fonctionnalité de suivi du calendrier des audits@TrackStatuses- ajoute un historique des changements de statut@Singleton- garantit qu'une seule instance d'une classe peut être créée
Quand utiliser les décorateurs de classe
Les décorateurs de classe sont particulièrement utiles :
- Lorsque tu veux ajouter une fonctionnalité transversale - lorsque la même fonctionnalité doit être ajoutée à de nombreuses classes (programmation orientée aspect)
- Lors de l'implémentation de patrons de conception - tels que Singleton, Factory, Observer
- Pour l'enregistrement automatique - par ex. enregistrer des composants dans un système d'injection de dépendances
- Pour la métaprogrammation - lorsque le code doit se modifier lui-même à l'exécution
- Pour ajouter des métadonnées - qui pourront ensuite être utilisées par un framework
Limites et problèmes des décorateurs de classe
Les décorateurs de classe ont aussi certaines limites et certains problèmes :
- Débogage plus difficile - un code comportant de nombreux décorateurs peut être plus difficile à déboguer
- Problèmes de typage - il faut parfois utiliser
as anypour contourner des problèmes de TypeScript - Fonctionnalité cachée - les décorateurs peuvent masquer des fonctionnalités importantes, rendant le code plus difficile à comprendre
- Ordre d'application - l'ordre d'application des décorateurs a son importance et peut poser problème
- Évolutions de la spécification - la spécification des décorateurs en JavaScript et TypeScript continue d'évoluer
Résumé
Les décorateurs de classe en TypeScript sont un outil puissant qui permet d'ajouter, de modifier ou de remplacer des fonctionnalités d'une classe de manière déclarative, sans toucher à son implémentation interne. Tout comme les protocoles de sécurité spéciaux de Jurassic Park aident à maintenir l'ordre et le contrôle, les décorateurs de classe aident à organiser et à étendre le code de façon claire et systématique.
Les décorateurs de classe sont surtout utiles dans les grandes applications où il faut souvent implémenter une fonctionnalité transversale à travers de nombreuses classes, comme la journalisation, l'autorisation, la validation ou l'enregistrement de composants. Grâce à eux, nous pouvons créer un code plus modulaire, flexible et facile à maintenir.
Souviens-toi cependant que, comme tout outil puissant, les décorateurs de classe doivent être utilisés avec discernement. En abuser peut conduire à un code difficile à déboguer et à comprendre. Utilise-les toujours lorsqu'ils résolvent réellement un problème, et non simplement parce qu'ils ont l'air élégants.
Code de cette leçon : index.ts
1// Système de types complet du parc - Parc Jurassique
2console.log("=== SYSTÈME DE TYPES AVANCÉ DU PARC ===\n");
3
4// ===========================================
5// 1. Types et interfaces de base
6// ===========================================
7
8interface Entity {
9 readonly id: string;
10 readonly createdAt: Date;
11}
12
13interface Dinosaur extends Entity {
14 name: string;
15 species: string;
16 weight: number;
17 diet: "carnivore" | "herbivore" | "omnivore";
18 dangerLevel: number;
19}
20
21interface Enclosure extends Entity {
22 name: string;
23 capacity: number;
24 securityLevel: number;
25 residents: string[];
26}
27
28interface StaffMember extends Entity {
29 name: string;
30 role: "ranger" | "veterinarian" | "researcher" | "security";
31 clearanceLevel: number;
32}
33
34// ===========================================
35// 2. Types mappés et conditionnels
36// ===========================================
37
38type EntityMap = {
39 dinosaur: Dinosaur;
40 enclosure: Enclosure;
41 staff: StaffMember;
42};
43
44type EntityType = keyof EntityMap;
45
46// Création automatique des types DTO
47type CreateDTO<T extends Entity> = Omit<T, "id" | "createdAt">;
48type UpdateDTO<T extends Entity> = Partial<Omit<T, "id" | "createdAt">>;
49type ReadDTO<T extends Entity> = Readonly<T>;
50
51// ===========================================
52// 3. Dépôt générique avec contraintes
53// ===========================================
54
55class Repository<T extends Entity> {
56 private storage: Map<string, T> = new Map();
57 private counter = 0;
58 private prefix: string;
59
60 constructor(prefix: string) {
61 this.prefix = prefix;
62 }
63
64 create(data: CreateDTO<T>): T {
65 const id = `${this.prefix}-${String(++this.counter).padStart(3, "0")}`;
66 const entity = { ...data, id, createdAt: new Date() } as T;
67 this.storage.set(id, entity);
68 return entity;
69 }
70
71 findById(id: string): ReadDTO<T> | undefined {
72 return this.storage.get(id);
73 }
74
75 findAll(): ReadDTO<T>[] {
76 return Array.from(this.storage.values());
77 }
78
79 update(id: string, data: UpdateDTO<T>): T | undefined {
80 const existing = this.storage.get(id);
81 if (!existing) return undefined;
82 const updated = { ...existing, ...data } as T;
83 this.storage.set(id, updated);
84 return updated;
85 }
86
87 delete(id: string): boolean {
88 return this.storage.delete(id);
89 }
90
91 query(predicate: (item: T) => boolean): ReadDTO<T>[] {
92 return this.findAll().filter(item => predicate(item as T));
93 }
94}
95
96// ===========================================
97// 4. Système d'événements avec types conditionnels
98// ===========================================
99
100type EventSeverity = "info" | "warning" | "critical";
101
102type ParkEvent<S extends EventSeverity> = S extends "critical"
103 ? { severity: S; message: string; requiresEvacuation: boolean; affectedZones: string[] }
104 : S extends "warning"
105 ? { severity: S; message: string; affectedZones: string[] }
106 : { severity: S; message: string };
107
108function createEvent<S extends EventSeverity>(
109 severity: S,
110 data: Omit<ParkEvent<S>, "severity">
111): ParkEvent<S> {
112 return { severity, ...data } as ParkEvent<S>;
113}
114
115// ===========================================
116// 5. Système de rapports avec types mappés
117// ===========================================
118
119type DinoReport = {
120 [K in keyof Omit<Dinosaur, "id" | "createdAt">]: {
121 label: string;
122 value: Dinosaur[K];
123 formatted: string;
124 };
125};
126
127function generateReport(dino: Dinosaur): DinoReport {
128 return {
129 name: { label: "Nom", value: dino.name, formatted: dino.name },
130 species: { label: "Espèce", value: dino.species, formatted: dino.species },
131 weight: { label: "Poids", value: dino.weight, formatted: `${dino.weight} kg` },
132 diet: { label: "Régime", value: dino.diet, formatted: dino.diet === "carnivore" ? "Carnivore" : dino.diet === "herbivore" ? "Herbivore" : "Omnivore" },
133 dangerLevel: { label: "Danger", value: dino.dangerLevel, formatted: `${dino.dangerLevel}/10` }
134 };
135}
136
137// ===========================================
138// Test
139// ===========================================
140
141// Dépôts
142const dinoRepo = new Repository<Dinosaur>("DINO");
143const encRepo = new Repository<Enclosure>("ENC");
144const staffRepo = new Repository<StaffMember>("STAFF");
145
146// Création des dinosaures
147console.log("--- Création des entités ---");
148const rex = dinoRepo.create({ name: "Rexy", species: "T-Rex", weight: 8000, diet: "carnivore", dangerLevel: 10 });
149const blue = dinoRepo.create({ name: "Blue", species: "Velociraptor", weight: 15, diet: "carnivore", dangerLevel: 8 });
150const patty = dinoRepo.create({ name: "Patty", species: "Brachiosaurus", weight: 50000, diet: "herbivore", dangerLevel: 1 });
151
152const mainEnc = encRepo.create({ name: "Zone T-Rex", capacity: 2, securityLevel: 10, residents: [rex.id] });
153const grant = staffRepo.create({ name: "Alan Grant", role: "researcher", clearanceLevel: 8 });
154
155console.log(`Dinosaur: ${rex.id} - ${rex.name}`);
156console.log(`Enclos: ${mainEnc.id} - ${mainEnc.name}`);
157console.log(`Employé: ${grant.id} - ${grant.name}`);
158
159// Mise à jour
160console.log("\n--- Mise à jour ---");
161const updatedRex = dinoRepo.update(rex.id, { weight: 8200 });
162console.log(`${updatedRex?.name}: ${updatedRex?.weight}kg`);
163
164// Requête
165console.log("\n--- Requêtes ---");
166const dangerousDinos = dinoRepo.query(d => d.dangerLevel >= 7);
167console.log("Dangereux:", dangerousDinos.map(d => d.name).join(", "));
168
169const carnivores = dinoRepo.query(d => d.diet === "carnivore");
170console.log("Carnivores:", carnivores.map(d => d.name).join(", "));
171
172// Événements
173console.log("\n--- Événements ---");
174const info = createEvent("info", { message: "Contrôle de routine terminé" });
175console.log("Info:", info.message);
176
177const warning = createEvent("warning", {
178 message: "Fence needs repair",
179 affectedZones: ["Zone A"]
180});
181console.log("Avertissement:", warning.message, "Zones:", warning.affectedZones.join(", "));
182
183const critical = createEvent("critical", {
184 message: "Évasion de dinosaure !",
185 requiresEvacuation: true,
186 affectedZones: ["Zone A", "Zone B"]
187});
188console.log("CRITIQUE:", critical.message, "Évacuation:", critical.requiresEvacuation);
189
190// Rapport
191console.log("\n--- Rapport du dinosaure ---");
192const report = generateReport(rex);
193Object.values(report).forEach(field => {
194 console.log(` ${field.label}: ${field.formatted}`);
195});
196
197console.log("\nSystème de types TypeScript complet en action!");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. Quel type utilitaire sert à EXCLURE des types précis d'un type union ?
2. Qu'est-ce qu'un décorateur en TypeScript ?
Tâches pratiques dans le jeu
- Tri vertical
Ordonne les étapes de création d'un type utilitaire personnalisé en TypeScript :
- Clique dans l'ordre
Range les éléments de la déclaration d'un décorateur de classe :