Cours JavaScript et TypeScript · Module 7: TypeScript avancé
Implémenter des interfaces
Dans cette leçon10
« À Jurassic Park, tout doit fonctionner selon des protocoles stricts », explique Claire Dearing, la directrice des opérations. « Chaque employé a un périmètre de responsabilités clairement défini, et chaque système doit répondre à des exigences précises. Peu nous importe comment exactement un garde surveille l'enclos des raptors - ce qui compte, c'est qu'il fasse son travail conformément aux procédures de sécurité. »
Dans le monde de TypeScript, les interfaces jouent un rôle similaire - elles définissent un « contrat » que les classes doivent respecter, sans préciser comment exactement elles doivent le faire. Dans cet exercice, tu apprendras à concevoir et à implémenter des interfaces en créant un système avancé de gestion pour Jurassic Park.
Les bases des interfaces
Une interface en TypeScript est une structure qui définit la forme d'un objet - quelles propriétés et méthodes il doit avoir, sans préciser leur implémentation. Tu peux voir une interface comme un contrat ou un protocole qu'une classe doit respecter.
Regardons la syntaxe de base d'une interface :
1// Interface definition
2interface ParkEmployee {
3 // Properties
4 id: string;
5 firstName: string;
6 lastName: string;
7 accessLevel: number;
8
9 // Methods
10 login(): void;
11 logout(): void;
12 checkPermissions(requiredLevel: number): boolean;
13}
14
15// Class implementing the interface
16class GuardParku implements ParkEmployee {
17 // Implementation of all required properties
18 id: string;
19 firstName: string;
20 lastName: string;
21 accessLevel: number;
22 private activeStatus: boolean = false;
23
24 constructor(id: string, firstName: string, lastName: string) {
25 this.id = id;
26 this.firstName = firstName;
27 this.lastName = lastName;
28 this.accessLevel = 2; // Niveau standard pour le guard
29 }
30
31 // Implementation of all required methods
32 login(): void {
33 console.log(`Guard ${this.firstName} ${this.lastName} logged into the system.`);
34 this.activeStatus = true;
35 }
36
37 logout(): void {
38 console.log(`Guard ${this.firstName} ${this.lastName} logged out of the system.`);
39 this.activeStatus = false;
40 }
41
42 checkPermissions(requiredLevel: number): boolean {
43 return this.accessLevel >= requiredLevel;
44 }
45
46 // Additional methods specific to the guard
47 patrolSector(sectorId: string): void {
48 if (!this.activeStatus) {
49 console.log("Error: Guard must be logged in to patrol sector.");
50 return;
51 }
52
53 console.log(`Guard ${this.firstName} ${this.lastName} is patrolling sector ${sectorId}.`);
54 }
55}
56
57// Another class implementing the same interface
58class ScientistParku implements ParkEmployee {
59 id: string;
60 firstName: string;
61 lastName: string;
62 accessLevel: number;
63 private specialization: string;
64
65 constructor(id: string, firstName: string, lastName: string, specialization: string) {
66 this.id = id;
67 this.firstName = firstName;
68 this.lastName = lastName;
69 this.accessLevel = 4; // Higher access level for scientists
70 this.specialization = specialization;
71 }
72
73 login(): void {
74 console.log(`Dr ${this.firstName} ${this.lastName} logged into the laboratory system.`);
75 }
76
77 logout(): void {
78 console.log(`Dr ${this.firstName} ${this.lastName} logged out of the laboratory system.`);
79 }
80
81 checkPermissions(requiredLevel: number): boolean {
82 // Scientists have special permissions in laboratories
83 if (requiredLevel === 3 && this.specialization === "genetics") {
84 return true;
85 }
86 return this.accessLevel >= requiredLevel;
87 }
88
89 // Method specific to the scientist
90 conductResearch(researchSubject: string): void {
91 console.log(`Dr ${this.firstName} ${this.lastName} is conducting research on: ${researchSubject}.`);
92 }
93}
94
95// Using classes implementing the interface
96const guard = new GuardParku("SEC-001", "John", "Smith");
97const scientist = new ScientistParku("SCI-042", "Henry", "Wu", "genetics");
98
99// Both classes implement the same interface, so we can use the same methods
100guard.login();
101scientist.login();
102
103// Checking permissions
104console.log(`Guard has access to zone 2: ${guard.checkPermissions(2)}`);
105console.log(`Scientist has access to zone 3: ${scientist.checkPermissions(3)}`);
106
107// Calling methods specific to particular classes
108guard.patrolSector("B-5");
109scientist.conductResearch("DNA Velociraptor");Dans l'exemple ci-dessus :
- Nous avons défini l'interface
ParkEmployeeprécisant quelles propriétés et méthodes chaque employé doit avoir - Nous avons créé deux classes différentes implémentant cette interface - chacune à sa manière
- Les deux classes doivent fournir toutes les propriétés et méthodes requises, mais peuvent les implémenter différemment
- Les classes peuvent aussi ajouter leurs propres méthodes et propriétés supplémentaires
Propriétés optionnelles et en lecture seule
Les interfaces TypeScript permettent des définitions de propriétés plus avancées :
1interface SecuritySystem {
2 // Required property
3 id: string;
4
5 // Read-only property - cannot be modified after initialization
6 readonly systemType: "monitoring" | "fence" | "motionSensor" | "alarm";
7
8 // Optional property - may or may not exist
9 advancedOptions?: Record<string, any>;
10
11 // Methods
12 activate(): void;
13 deactivate(): void;
14 checkStatus(): string;
15}
16
17class FenceSystem implements SecuritySystem {
18 id: string;
19 readonly systemType: "monitoring" | "fence" | "motionSensor" | "alarm" = "fence";
20 advancedOptions?: Record<string, any>; // Optional property
21
22 private voltage: number = 0;
23 private active: boolean = false;
24
25 constructor(id: string, advancedOptions?: Record<string, any>) {
26 this.id = id;
27 if (advancedOptions) {
28 this.advancedOptions = advancedOptions;
29 }
30 }
31
32 activate(): void {
33 this.active = true;
34 this.voltage = this.advancedOptions?.startingVoltage || 10000; // Using optional property
35 console.log(`System fence ${this.id} activated. Voltage: ${this.voltage}V`);
36 }
37
38 deactivate(): void {
39 this.active = false;
40 this.voltage = 0;
41 console.log(`Fence system ${this.id} deactivated.`);
42 }
43
44 checkStatus(): string {
45 return `System fence ${this.id}: ${this.active ? "ACTIVE" : "INACTIVE"}, Voltage: ${this.voltage}V`;
46 }
47
48 // Custom method, not required by the interface
49 increaseVoltage(increment: number): void {
50 if (!this.active) {
51 console.log("Cannot change voltage - system inactive.");
52 return;
53 }
54
55 this.voltage += increment;
56 console.log(`Increased voltage to ${this.voltage}V.`);
57 }
58}
59
60// Usage with optional properties
61const tRexFence = new FenceSystem("FNC-TR1", {
62 startingVoltage: 20000,
63 automaticRestarts: true
64});
65
66const standardFence = new FenceSystem("FNC-ST1");
67
68tRexFence.activate(); // Uses custom starting voltage
69standardFence.activate(); // Uses default starting voltage
70
71// Error: Cannot assign to 'systemType' because it is a read-only property
72// tRexFence.systemType = "alarm";Dans cet exemple :
- Nous avons défini une propriété
readonlyqui ne peut pas être modifiée après l'initialisation - Nous avons ajouté une propriété optionnelle
advancedOptions(marquée d'un?) - L'implémentation vérifie si la propriété optionnelle existe avant de l'utiliser
Étendre des interfaces
Les interfaces, comme les classes, peuvent étendre d'autres interfaces, en héritant de leurs propriétés et méthodes :
1// Basic interface for all animals
2interface Animal {
3 id: string;
4 species: string;
5 weight: number;
6 karma(): void;
7 monitor(): string;
8}
9
10// Extended interface specific to dinosaurs
11interface Dinosaur extends Animal {
12 dangerLevel: number;
13 dinosaurEra: "trias" | "jura" | "cretaceous";
14 getSpecialDiet(): string;
15}
16
17// Even more specialized interface
18interface PredatoryDinosaur extends Dinosaur {
19 biteForce: number;
20 speed: number;
21 hunt(): void;
22}
23
24// Implementation
25class TyrannosaurusRex implements PredatoryDinosaur {
26 // Properties from Animal interface
27 id: string;
28 species: string = "Tyrannosaurus Rex";
29 weight: number;
30
31 // Properties from Dinosaur interface
32 dangerLevel: number = 10;
33 dinosaurEra: "trias" | "jura" | "cretaceous" = "cretaceous";
34
35 // Properties from PredatoryDinosaur interface
36 biteForce: number;
37 speed: number;
38
39 // Custom properties
40 private age: number;
41 private lastFeeding: Date | null = null;
42
43 constructor(id: string, weight: number, age: number, biteForce: number, speed: number) {
44 this.id = id;
45 this.weight = weight;
46 this.age = age;
47 this.biteForce = biteForce;
48 this.speed = speed;
49 }
50
51 // Implementation of methods from Animal interface
52 karma(): void {
53 this.lastFeeding = new Date();
54 console.log(`T-Rex ${this.id} was fed.`);
55 }
56
57 monitor(): string {
58 const feedingStatus = this.lastFeeding
59 ? `Last feeding: ${this.lastFeeding.toLocaleString()}`
60 : "No feeding information";
61
62 return `T-Rex ${this.id}: Weight: ${this.weight}kg, Age: ${this.age} years. ${feedingStatus}`;
63 }
64
65 // Implementation of method from Dinosaur interface
66 getSpecialDiet(): string {
67 return "Fresh mammal meat, preferably beef, 250kg daily.";
68 }
69
70 // Implementation of method from PredatoryDinosaur interface
71 hunt(): void {
72 console.log(`T-Rex ${this.id} hunts at speed ${this.speed} km/h and bite force ${this.biteForce} PSI!`);
73 }
74}
75
76// Usage
77const rex = new TyrannosaurusRex("TREX-01", 8000, 12, 12800, 30);
78rex.karma();
79console.log(rex.monitor());
80console.log(`Special diet: ${rex.getSpecialDiet()}`);
81rex.hunt();Dans cet exemple, nous avons créé une hiérarchie d'interfaces :
Animal- l'interface de base pour tous les animauxDinosaur- étendAnimalavec des propriétés propres aux dinosauresPredatoryDinosaur- étendDinosauravec des propriétés propres aux prédateurs
La classe TyrannosaurusRex implémente l'interface la plus spécialisée PredatoryDinosaur, ce qui signifie qu'elle doit fournir toutes les propriétés et méthodes des trois interfaces.
Interfaces vs types
TypeScript propose aussi les alias de type (type), qui ont des capacités similaires aux interfaces, mais il existe quelques différences entre eux :
1// Interface
2interface EnclosureMeasurement {
3 width: number;
4 length: number;
5 calculateArea(): number;
6}
7
8// Type (Type alias)
9type CageMeasurement = {
10 width: number;
11 height: number;
12 depth: number;
13 calculateVolume(): number;
14};
15
16// Extending an interface
17interface EnclosureMeasurementWithFence extends EnclosureMeasurement {
18 fenceHeight: number;
19 fenceMaterial: string;
20}
21
22// "Extending" a type through intersection
23type ReinforcedCageMeasurement = CageMeasurement & {
24 reinforcementMaterial: string;
25 maxLoad: number;
26};
27
28// Implementation of interface
29class RaptorEnclosure implements EnclosureMeasurementWithFence {
30 width: number;
31 length: number;
32 fenceHeight: number;
33 fenceMaterial: string;
34
35 constructor(width: number, length: number, fenceHeight: number) {
36 this.width = width;
37 this.length = length;
38 this.fenceHeight = fenceHeight;
39 this.fenceMaterial = "Reinforced steel";
40 }
41
42 calculateArea(): number {
43 return this.width * this.length;
44 }
45}
46
47// Implementation of type (works the same as interface)
48class TransportCage implements ReinforcedCageMeasurement {
49 width: number;
50 height: number;
51 depth: number;
52 reinforcementMaterial: string;
53 maxLoad: number;
54
55 constructor(
56 width: number,
57 height: number,
58 depth: number,
59 maxLoad: number
60 ) {
61 this.width = width;
62 this.height = height;
63 this.depth = depth;
64 this.reinforcementMaterial = "Carbon composite";
65 this.maxLoad = maxLoad;
66 }
67
68 calculateVolume(): number {
69 return this.width * this.height * this.depth;
70 }
71}Principales différences entre interfaces et types :
Extensibilité :
- Les interfaces peuvent être étendues ultérieurement (fusion de déclarations)
- Les types sont fermés après leur définition
1// Interface can be extended with additional declarations 2interface DinosaurDiagnosis { 3 examinationResult: string; 4} 5 6// Adding new properties to an existing interface 7interface DinosaurDiagnosis { 8 executionDate: Date; 9} 10 11// Now DinosaurDiagnosis has both properties! 12 13// For types this won't work: 14type EnclosureDescription = { 15 id: string; 16}; 17 18// Error: Duplicate identifier 'EnclosureDescription' 19// type EnclosureDescription = { 20// name: string; 21// };Combinaison :
- Les interfaces utilisent
extends - Les types utilisent l'opérateur d'intersection
&
- Les interfaces utilisent
Constructions avancées :
- Les types peuvent représenter des unions, des intersections et d'autres types complexes
- Les interfaces se limitent à décrire la forme des objets
1// Advanced types that cannot be expressed with interfaces
2type DinosaurStatus = "healthy" | "sick" | "in_treatment" | "quarantined";
3
4type DinosaurID = string | number;
5
6type AnimalOrPlant = Animal | { genus: string, height: number };
7
8// Mapped types
9type Optional<T> = {
10 [P in keyof T]?: T[P];
11};
12
13// Using a mapped type with an interface
14type OptionalDinosaur = Optional<Dinosaur>;Interfaces pour les fonctions et les méthodes
Les interfaces peuvent aussi définir la forme des fonctions et des constructeurs de classe :
1// Interface for a function
2interface PhotoProcessing {
3 (path: string, options?: { resolution?: number, filter?: string }): Promise<string>;
4}
5
6// Implementation of a function conforming to the interface
7const dinosaurPhotoProcessor: PhotoProcessing = async (path, options) => {
8 console.log(`Processing photo: ${path}`);
9 console.log(`Options: ${JSON.stringify(options || {})}`);
10
11 // Simulation of processing
12 await new Promise(resolve => setTimeout(resolve, 1000));
13
14 return `optimized_${path}`;
15};
16
17// Calling the function
18dinosaurPhotoProcessor("trex.jpg", { resolution: 1200 })
19 .then(result => console.log(`Processed: ${result}`));
20
21// Interface for a class constructor
22interface DinosaurConstructor {
23 new (id: string, species: string, age: number): Animal;
24}
25
26// Function using a constructor
27function createDinosaur(Constructor: DinosaurConstructor, id: string, species: string, age: number): Animal {
28 return new Constructor(id, species, age);
29}
30
31// Class conforming to the constructor interface
32class DinosaurPrototype implements Animal {
33 id: string;
34 species: string;
35 weight: number = 0;
36 private _age: number;
37
38 constructor(id: string, species: string, age: number) {
39 this.id = id;
40 this.species = species;
41 this._age = age;
42
43 // Weight calculation based on age
44 this.weight = age * 100; // Simplified calculation
45 }
46
47 karma(): void {
48 console.log(`Feeding prototype ${this.species}.`);
49 }
50
51 monitor(): string {
52 return `Monitoring prototype ${this.species} (ID: ${this.id}).`;
53 }
54}
55
56// Using the factory function
57const newDinosaur = createDinosaur(DinosaurPrototype, "PROTO-1", "Testosaurus", 5);
58console.log(newDinosaur.monitor());Signatures d'index dans les interfaces
Les interfaces peuvent aussi définir des types indexés qui permettent un accès dynamique aux propriétés :
1// Interface with index signatures
2interface AnimalDatabase {
3 // String index, values of type Animal
4 [id: string]: Animal;
5
6 // We can also add specific properties
7 animalCount: number;
8 lastUpdate: Date;
9}
10
11// Implementation
12class AnimalRepository implements AnimalDatabase {
13 // Implementation of index
14 [id: string]: Animal | number | Date | Function;
15
16 animalCount: number = 0;
17 lastUpdate: Date = new Date();
18
19 // We need to handle methods, because they are also properties
20 addAnimal(animal: Animal): void {
21 this[animal.id] = animal;
22 this.animalCount++;
23 this.lastUpdate = new Date();
24 }
25
26 getAnimal(id: string): Animal | undefined {
27 const animal = this[id];
28 if (animal && typeof animal !== 'number' && !(animal instanceof Date) && typeof animal !== 'function') {
29 return animal;
30 }
31 return undefined;
32 }
33}
34
35// Usage
36const animalRepo = new AnimalRepository();
37
38// Adding some animals
39const triceratops = {
40 id: "TRIC-01",
41 species: "Triceratops",
42 weight: 6000,
43 karma(): void { console.log("Feeding Triceratops"); },
44 monitor(): string { return "Monitoring Triceratops"; }
45};
46
47const stegosaurus = {
48 id: "STEG-01",
49 species: "Stegosaurus",
50 weight: 5000,
51 karma(): void { console.log("Feeding Stegosaurus"); },
52 monitor(): string { return "Monitoring Stegosaurus"; }
53};
54
55animalRepo.addAnimal(triceratops);
56animalRepo.addAnimal(stegosaurus);
57
58// Access through indexes
59const animal = animalRepo.getAnimal("TRIC-01");
60if (animal) {
61 console.log(`Found: ${animal.species}`);
62 animal.karma();
63}
64
65console.log(`Number of animals in repository: ${animalRepo.animalCount}`);
66console.log(`Last update: ${animalRepo.lastUpdate.toLocaleString()}`);Implémenter plusieurs interfaces
Une classe peut implémenter plusieurs interfaces en même temps :
1// Interface for monitoring capability
2interface Monitorable {
3 startMonitoring(): void;
4 stopMonitoring(): void;
5 getMonitoringStatus(): string;
6}
7
8// Interface for alarm systems
9interface AlarmSystem {
10 startAlarm(): void;
11 disableAlarm(): void;
12 isAlarmActive: boolean;
13}
14
15// Interface for access control systems
16interface AccessControl {
17 checkAccess(permissionLevel: number): boolean;
18 blockAccess(): void;
19 unblockAccess(): void;
20}
21
22// Class implementing all three interfaces
23class AdvancedSecuritySystem implements Monitorable, AlarmSystem, AccessControl {
24 private id: string;
25 private activeMonitoring: boolean = false;
26 isAlarmActive: boolean = false;
27 private accessBlocked: boolean = false;
28 private minimumPermissionLevel: number;
29
30 constructor(id: string, minimumPermissionLevel: number = 3) {
31 this.id = id;
32 this.minimumPermissionLevel = minimumPermissionLevel;
33 }
34
35 // Implementation of Monitorable
36 startMonitoring(): void {
37 this.activeMonitoring = true;
38 console.log(`System ${this.id}: Monitoring started.`);
39 }
40
41 stopMonitoring(): void {
42 this.activeMonitoring = false;
43 console.log(`System ${this.id}: Monitoring stopped.`);
44 }
45
46 getMonitoringStatus(): string {
47 return `System ${this.id}: Monitoring ${this.activeMonitoring ? "active" : "inactive"}`;
48 }
49
50 // Implementation of AlarmSystem
51 startAlarm(): void {
52 this.isAlarmActive = true;
53 this.blockAccess(); // Automatic access block
54 console.log(`System ${this.id}: !!! ALARM STARTED !!!`);
55 }
56
57 disableAlarm(): void {
58 this.isAlarmActive = false;
59 console.log(`System ${this.id}: Alarm disabled.`);
60 }
61
62 // Implementation of AccessControl
63 checkAccess(permissionLevel: number): boolean {
64 if (this.accessBlocked) {
65 console.log(`System ${this.id}: Access blocked for all permission levels.`);
66 return false;
67 }
68
69 const accessGranted = permissionLevel >= this.minimumPermissionLevel;
70 console.log(`System ${this.id}: Checking access for level ${permissionLevel}: ${accessGranted ? "GRANTED" : "DENIED"}`);
71 return accessGranted;
72 }
73
74 blockAccess(): void {
75 this.accessBlocked = true;
76 console.log(`System ${this.id}: Access blocked.`);
77 }
78
79 unblockAccess(): void {
80 if (this.isAlarmActive) {
81 console.log(`System ${this.id}: Cannot unblock access - alarm active!`);
82 return;
83 }
84
85 this.accessBlocked = false;
86 console.log(`System ${this.id}: Access unblocked.`);
87 }
88
89 // Custom method combining functionalities from different interfaces
90 respondToBreakIn(): void {
91 console.log(`System ${this.id}: Break-in attempt detected!`);
92 this.startMonitoring();
93 this.startAlarm();
94 // Access is already blocked by startAlarm()
95 }
96}
97
98// Using a class implementing multiple interfaces
99const labSecuritySystem = new AdvancedSecuritySystem("SEC-LAB-01", 4);
100
101// We can use methods from different interfaces
102labSecuritySystem.startMonitoring();
103console.log(labSecuritySystem.getMonitoringStatus());
104
105// Checking access
106console.log(labSecuritySystem.checkAccess(3)); // Denied - required level 4
107console.log(labSecuritySystem.checkAccess(5)); // Access granted
108
109// Reaction to break-in
110labSecuritySystem.respondToBreakIn();
111
112// Attempting to unblock access during alarm
113labSecuritySystem.unblockAccess(); // Will not allow unlock
114
115// Disabling alarm and unblocking
116labSecuritySystem.disableAlarm();
117labSecuritySystem.unblockAccess();Implémenter des interfaces avec des conflits de noms
Que se passe-t-il lorsqu'on implémente deux interfaces ayant des propriétés du même nom ?
1// Two interfaces with properties of the same name
2interface SystemA {
3 id: string;
4 status: string;
5 activate(): void;
6}
7
8interface SystemB {
9 id: number; // Type conflict - string vs number
10 status: string; // Same type, no conflict
11 activate(code: string): boolean; // Signature conflict
12}
13
14// Attempting to implement both interfaces
15// This implementation will cause a compilation error
16/*
17class ProblematicSituation implements SystemA, SystemB {
18 id: string; // Cannot be both string and number
19 status: string; // OK, same type in both interfaces
20
21 // Attempting to implement both activate signatures
22 activate(code?: string): boolean | void {
23 if (code) {
24 console.log(`Activation with code: ${code}`);
25 return true;
26 } else {
27 console.log("Activation without code");
28 }
29 }
30}
31*/
32
33// Correct implementation:
34// We need to create a type compatible with both interfaces
35interface CombinedSystem extends SystemA, SystemB {
36 // Resolving the id conflict
37 id: string & number; // Must be both string and number (impossible in practice)
38
39 // Status without conflict
40 status: string;
41
42 // Resolving the method conflict
43 activate(code?: string): boolean | void;
44}
45
46// In practice, it's often impossible to implement both interfaces simultaneously
47// In such cases, it's better to modify one of the interfaces or
48// create an adapter that delegates callsComment résoudre le problème des interfaces incompatibles :
1// Better approach: Using composition instead of inheritance
2class AccessSystem {
3 private systemA: SystemAImplementation;
4 private systemB: SystemBImplementation;
5
6 constructor() {
7 this.systemA = new SystemAImplementation("SYS-A");
8 this.systemB = new SystemBImplementation(1001);
9 }
10
11 // Methods delegating to appropriate implementations
12 activateA(): void {
13 this.systemA.activate();
14 }
15
16 activateB(code: string): boolean {
17 return this.systemB.activate(code);
18 }
19
20 getStatusA(): string {
21 return this.systemA.status;
22 }
23
24 getStatusB(): string {
25 return this.systemB.status;
26 }
27}
28
29// Single interface implementations
30class SystemAImplementation implements SystemA {
31 id: string;
32 status: string = "inactive";
33
34 constructor(id: string) {
35 this.id = id;
36 }
37
38 activate(): void {
39 this.status = "active";
40 console.log(`System A (${this.id}) activated`);
41 }
42}
43
44class SystemBImplementation implements SystemB {
45 id: number;
46 status: string = "offline";
47
48 constructor(id: number) {
49 this.id = id;
50 }
51
52 activate(code: string): boolean {
53 if (code === "secret-password") {
54 this.status = "online";
55 console.log(`System B (${this.id}) activated with code`);
56 return true;
57 }
58 console.log(`System B (${this.id}) - incorrect activation code`);
59 return false;
60 }
61}Exemple pratique : système de gestion de Jurassic Park
Voici un exemple complet utilisant des interfaces pour créer un système de gestion de Jurassic Park :
1// Interface for a basic manageable entity
2interface InventoryItem {
3 readonly id: string;
4 name: string;
5 category: string;
6 additionDate: Date;
7 lastUpdate: Date;
8 status: "active" | "inactive" | "under_repair" | "retired";
9
10 updateStatus(newStatus: "active" | "inactive" | "under_repair" | "retired"): void;
11 getInfo(): Record<string, any>;
12}
13
14// Interface for trackable objects
15interface Tracked {
16 currentLocation: { x: number, y: number } | null;
17 locationHistory: Array<{ time: Date, x: number, y: number }>;
18
19 updateLocation(x: number, y: number): void;
20 getLastLocation(): { time: Date, x: number, y: number } | null;
21 getMovementRoute(fromTime: Date, toTime?: Date): Array<{ time: Date, x: number, y: number }>;
22}
23
24// Interface for monitorable objects
25interface Monitored {
26 monitoredParameters: Map<string, number | string | boolean>;
27 measurementHistory: Array<{ time: Date, parameter: string, value: number | string | boolean }>;
28 monitoringStatus: "enabled" | "disabled" | "limited";
29
30 startMonitoring(): void;
31 stopMonitoring(): void;
32 addMeasurement(parameter: string, value: number | string | boolean): void;
33 getRecentMeasurements(count?: number): Array<{ time: Date, parameter: string, value: number | string | boolean }>;
34}
35
36// Interface for objects requiring regular maintenance
37interface Maintainable {
38 requiredMaintenanceLevel: number; // 1-5
39 lastMaintenance: Date | null;
40 plannedMaintenance: Date | null;
41 maintenanceHistory: Array<{ data: Date, description: string, contractor: string }>;
42
43 scheduleMaintenance(data: Date): void;
44 performMaintenance(description: string, contractor: string): void;
45 checkMaintenanceStatus(): "required" | "scheduled" | "current";
46}
47
48// Interface for security systems
49interface SecuritySystem {
50 threatLevel: 1 | 2 | 3 | 4 | 5;
51 securityZone: string;
52 authorizedPersons: string[];
53
54 raiseThreatLevel(): void;
55 lowerThreatLevel(): void;
56 checkPermissions(employeeId: string): boolean;
57 respondToThreat(description: string): void;
58}
59
60// Implementation of a dinosaur tracking device
61class TrackingDeviceT3000 implements InventoryItem, Tracked, Monitored, Maintainable {
62 // InventoryItem (Inventory Item)
63 readonly id: string;
64 name: string;
65 category: string = "Tracking Device";
66 additionDate: Date;
67 lastUpdate: Date;
68 status: "active" | "inactive" | "under_repair" | "retired";
69
70 // Tracked (Tracked)
71 currentLocation: { x: number, y: number } | null = null;
72 locationHistory: Array<{ time: Date, x: number, y: number }> = [];
73
74 // Monitored (Monitored)
75 monitoredParameters: Map<string, number | string | boolean> = new Map();
76 measurementHistory: Array<{ time: Date, parameter: string, value: number | string | boolean }> = [];
77 monitoringStatus: "enabled" | "disabled" | "limited" = "disabled";
78
79 // Maintainable (Maintainable)
80 requiredMaintenanceLevel: number = 3;
81 lastMaintenance: Date | null = null;
82 plannedMaintenance: Date | null = null;
83 maintenanceHistory: Array<{ data: Date, description: string, contractor: string }> = [];
84
85 // Custom properties
86 private batteryLevel: number = 100; // in percent
87 private assignedDinosaur: string | null = null;
88
89 constructor(id: string, name: string) {
90 this.id = id;
91 this.name = name;
92 this.additionDate = new Date();
93 this.lastUpdate = new Date();
94 this.status = "inactive";
95
96 // Initializing monitoring parameters
97 this.monitoredParameters.set("battery", this.batteryLevel);
98 this.monitoredParameters.set("active", false);
99 this.monitoredParameters.set("temperature", 25.0);
100 }
101
102 // Implementation of InventoryItem methods
103 updateStatus(newStatus: "active" | "inactive" | "under_repair" | "retired"): void {
104 this.status = newStatus;
105 this.lastUpdate = new Date();
106 console.log(`Device ${this.id}: Changed status to ${newStatus}`);
107 }
108
109 getInfo(): Record<string, any> {
110 return {
111 id: this.id,
112 name: this.name,
113 category: this.category,
114 status: this.status,
115 additionDate: this.additionDate,
116 lastUpdate: this.lastUpdate,
117 batteryLevel: this.batteryLevel,
118 assignedDinosaur: this.assignedDinosaur
119 };
120 }
121
122 // Implementation of Tracked methods
123 updateLocation(x: number, y: number): void {
124 const time = new Date();
125
126 // Updating current location
127 this.currentLocation = { x, y };
128
129 // Adding to history
130 this.locationHistory.push({ time, x, y });
131
132 // Simulating battery drain
133 this.reduceBattery(0.1);
134
135 console.log(`Device ${this.id}: Updated location to [x: ${x}, y: ${y}]`);
136 }
137
138 getLastLocation(): { time: Date, x: number, y: number } | null {
139 if (this.locationHistory.length === 0) {
140 return null;
141 }
142
143 return this.locationHistory[this.locationHistory.length - 1];
144 }
145
146 getMovementRoute(fromTime: Date, toTime: Date = new Date()): Array<{ time: Date, x: number, y: number }> {
147 return this.locationHistory.filter(loc =>
148 loc.time >= fromTime && loc.time <= toTime
149 );
150 }
151
152 // Implementation of Monitored methods
153 startMonitoring(): void {
154 if (this.monitoringStatus !== "enabled") {
155 this.monitoringStatus = "enabled";
156 console.log(`Device ${this.id}: Monitoring started`);
157
158 // Simulating measurement when monitoring is enabled
159 this.addMeasurement("active", true);
160 }
161 }
162
163 stopMonitoring(): void {
164 if (this.monitoringStatus !== "disabled") {
165 this.monitoringStatus = "disabled";
166 console.log(`Device ${this.id}: Monitoring stopped`);
167
168 // Updating the activity parameter
169 this.monitoredParameters.set("active", false);
170 this.addMeasurement("active", false);
171 }
172 }
173
174 addMeasurement(parameter: string, value: number | string | boolean): void {
175 const time = new Date();
176
177 // Updating the current parameter value
178 this.monitoredParameters.set(parameter, value);
179
180 // Adding to measurement history
181 this.measurementHistory.push({ time, parameter, value });
182
183 // Updating the last update time
184 this.lastUpdate = time;
185
186 console.log(`Device ${this.id}: Added pomiar ${parameter} = ${value}`);
187 }
188
189 getRecentMeasurements(count: number = 10): Array<{ time: Date, parameter: string, value: number | string | boolean }> {
190 // Returns the specified number of most recent measurements
191 return this.measurementHistory.slice(-count);
192 }
193
194 // Implementation of Maintainable methods
195 scheduleMaintenance(data: Date): void {
196 this.plannedMaintenance = data;
197 console.log(`Device ${this.id}: Scheduled maintenance for ${data.toLocaleDateString()}`);
198 }
199
200 performMaintenance(description: string, contractor: string): void {
201 const date = new Date();
202
203 // Adding to maintenance history
204 this.maintenanceHistory.push({ date, description, contractor });
205
206 // Updating the last maintenance date
207 this.lastMaintenance = date;
208
209 // Resetting planned maintenance
210 this.plannedMaintenance = null;
211
212 // Updating status and battery after maintenance
213 this.updateStatus("active");
214 this.batteryLevel = 100;
215 this.monitoredParameters.set("battery", this.batteryLevel);
216 this.addMeasurement("battery", this.batteryLevel);
217
218 console.log(`Device ${this.id}: Maintenance performed by ${contractor}: ${description}`);
219 }
220
221 checkMaintenanceStatus(): "required" | "scheduled" | "current" {
222 // No previous maintenance - required
223 if (!this.lastMaintenance) {
224 return "required";
225 }
226
227 // Planned maintenance in the future
228 if (this.plannedMaintenance && this.plannedMaintenance > new Date()) {
229 return "scheduled";
230 }
231
232 // Checking if more than 90 days have passed since the last maintenance
233 const today = new Date();
234 const differenceMilliseconds = today.getTime() - this.lastMaintenance.getTime();
235 const differenceDays = differenceMilliseconds / (1000 * 60 * 60 * 24);
236
237 if (differenceDays > 90 || this.batteryLevel < 20) {
238 return "required";
239 }
240
241 return "current";
242 }
243
244 // Custom methods
245 assignDinosaur(dinosaurId: string): void {
246 this.assignedDinosaur = dinosaurId;
247 this.addMeasurement("assignedDinosaur", dinosaurId);
248 console.log(`Device ${this.id}: Assigned to dinosaur ${dinosaurId}`);
249 }
250
251 private reduceBattery(value: number): void {
252 this.batteryLevel = Math.max(0, this.batteryLevel - value);
253 this.monitoredParameters.set("battery", this.batteryLevel);
254
255 // If battery is critically low - change status
256 if (this.batteryLevel < 5 && this.status === "active") {
257 this.updateStatus("inactive");
258 console.log(`Device ${this.id}: Critical battery level! Device disabled.`);
259 }
260 }
261}
262
263// Implementation of dinosaur enclosure security system
264class ElectricFence implements InventoryItem, SecuritySystem, Maintainable {
265 // InventoryItem (Inventory Item)
266 readonly id: string;
267 name: string;
268 category: string = "Fence Electric";
269 additionDate: Date;
270 lastUpdate: Date;
271 status: "active" | "inactive" | "under_repair" | "retired";
272
273 // SecuritySystem (Security System)
274 threatLevel: 1 | 2 | 3 | 4 | 5 = 1;
275 securityZone: string;
276 authorizedPersons: string[] = [];
277
278 // Maintainable (Maintainable)
279 requiredMaintenanceLevel: number = 5; // Highest level - critical for security
280 lastMaintenance: Date | null = null;
281 plannedMaintenance: Date | null = null;
282 maintenanceHistory: Array<{ data: Date, description: string, contractor: string }> = [];
283
284 // Custom properties
285 private voltage: number; // in volts
286 private fenceLength: number; // in meters
287 private motionSensors: boolean;
288
289 constructor(
290 id: string,
291 name: string,
292 securityZone: string,
293 fenceLength: number,
294 voltage: number = 10000,
295 motionSensors: boolean = true
296 ) {
297 this.id = id;
298 this.name = name;
299 this.securityZone = securityZone;
300 this.fenceLength = fenceLength;
301 this.voltage = voltage;
302 this.motionSensors = motionSensors;
303
304 this.additionDate = new Date();
305 this.lastUpdate = new Date();
306 this.status = "inactive";
307
308 // Adding system administrators as authorized personnel
309 this.authorizedPersons.push("ADMIN-001", "SEC-CHIEF", "TECH-HEAD");
310 }
311
312 // Implementation of InventoryItem
313 updateStatus(newStatus: "active" | "inactive" | "under_repair" | "retired"): void {
314 // If switching from inactive to active
315 if (this.status !== "active" && newStatus === "active") {
316 // Check if maintenance is required
317 if (this.checkMaintenanceStatus() === "required") {
318 console.log(`Fence ${this.id}: WARNING - Activation without required maintenance!`);
319 }
320 }
321
322 this.status = newStatus;
323 this.lastUpdate = new Date();
324
325 // Automatically adjust voltage based on status
326 if (newStatus === "active") {
327 console.log(`Fence ${this.id}: Activated with voltage ${this.voltage}V`);
328 } else if (newStatus === "inactive" || newStatus === "under_repair") {
329 this.voltage = 0;
330 console.log(`Fence ${this.id}: Deactivated - voltage 0V`);
331 }
332 }
333
334 getInfo(): Record<string, any> {
335 return {
336 id: this.id,
337 name: this.name,
338 category: this.category,
339 status: this.status,
340 securityZone: this.securityZone,
341 fenceLength: this.fenceLength,
342 voltage: this.voltage,
343 motionSensors: this.motionSensors,
344 threatLevel: this.threatLevel
345 };
346 }
347
348 // Implementation of SecuritySystem
349 raiseThreatLevel(): void {
350 if (this.threatLevel < 5) {
351 this.threatLevel = (this.threatLevel + 1) as 1 | 2 | 3 | 4 | 5;
352
353 // Automatic voltage increase
354 this.increaseVoltage(2000 * this.threatLevel);
355
356 console.log(`Fence ${this.id}: Raised threat level to ${this.threatLevel}. New voltage: ${this.voltage}V`);
357 } else {
358 console.log(`Fence ${this.id}: Threat level already at maximum level 5`);
359 }
360 }
361
362 lowerThreatLevel(): void {
363 if (this.threatLevel > 1) {
364 this.threatLevel = (this.threatLevel - 1) as 1 | 2 | 3 | 4 | 5;
365
366 // Automatic voltage decrease (but still active)
367 const newVoltage = 10000 + (2000 * (this.threatLevel - 1));
368 this.voltage = newVoltage;
369
370 console.log(`Fence ${this.id}: Lowered threat level to ${this.threatLevel}. New voltage: ${this.voltage}V`);
371 } else {
372 console.log(`Fence ${this.id}: Threat level already at minimum level 1`);
373 }
374 }
375
376 checkPermissions(employeeId: string): boolean {
377 return this.authorizedPersons.includes(employeeId);
378 }
379
380 respondToThreat(description: string): void {
381 console.log(`Fence ${this.id}: ALERT! ${description}`);
382
383 // Maximum threat level
384 this.threatLevel = 5;
385
386 // Maximum voltage
387 this.voltage = 30000;
388
389 console.log(`Fence ${this.id}: Threat level 5. Maximum voltage: ${this.voltage}V`);
390 }
391
392 // Implementation of Maintainable
393 scheduleMaintenance(data: Date): void {
394 this.plannedMaintenance = data;
395 console.log(`Fence ${this.id}: Scheduled maintenance for ${date.toLocaleDateString()}`);
396 }
397
398 performMaintenance(description: string, contractor: string): void {
399 const date = new Date();
400
401 // Safety measure - temporary fence deactivation during maintenance
402 const previousStatus = this.status;
403 this.updateStatus("under_repair");
404
405 // Add to maintenance history
406 this.maintenanceHistory.push({ date, description, contractor });
407
408 // Update last maintenance date
409 this.lastMaintenance = date;
410
411 // Reset planned maintenance
412 this.plannedMaintenance = null;
413
414 console.log(`Fence ${this.id}: Maintenance performed by ${contractor}: ${description}`);
415
416 // Restoring previous status
417 this.updateStatus(previousStatus);
418 }
419
420 checkMaintenanceStatus(): "required" | "scheduled" | "current" {
421 // No prior maintenance - required
422 if (!this.lastMaintenance) {
423 return "required";
424 }
425
426 // Maintenance scheduled in the future
427 if (this.plannedMaintenance && this.plannedMaintenance > new Date()) {
428 return "scheduled";
429 }
430
431 // Checking if more than 30 days have passed since last maintenance (shorter period for fences)
432 const today = new Date();
433 const differenceMilliseconds = today.getTime() - this.lastMaintenance.getTime();
434 const differenceDays = differenceMilliseconds / (1000 * 60 * 60 * 24);
435
436 if (differenceDays > 30) {
437 return "required";
438 }
439
440 return "current";
441 }
442
443 // Custom methods
444 addAuthorizedPerson(employeeId: string): void {
445 if (!this.authorizedPersons.includes(employeeId)) {
446 this.authorizedPersons.push(employeeId);
447 console.log(`Enclosure ${this.id}: Added permissions for employee ${employeeId}`);
448 }
449 }
450
451 removeAuthorizedPerson(employeeId: string): void {
452 const index = this.authorizedPersons.indexOf(employeeId);
453 if (index !== -1) {
454 this.authorizedPersons.splice(index, 1);
455 console.log(`Fence ${this.id}: Removed permissions for employee ${employeeId}`);
456 }
457 }
458
459 increaseVoltage(value: number): void {
460 if (this.status !== "active") {
461 console.log(`Fence ${this.id}: Cannot change voltage - fence is not active`);
462 return;
463 }
464
465 this.voltage += value;
466 console.log(`Fence ${this.id}: Increased voltage to ${this.voltage}V`);
467 }
468
469 decreaseVoltage(value: number): void {
470 if (this.status !== "active") {
471 console.log(`Fence ${this.id}: Cannot change voltage - fence is not active`);
472 return;
473 }
474
475 this.voltage = Math.max(0, this.voltage - value);
476 console.log(`Fence ${this.id}: Decreased voltage to ${this.voltage}V`);
477 }
478}
479
480// Demonstrating the use of our implementations
481function demonstrateParkSystem() {
482 console.log("=== JURASSIC PARK MANAGEMENT SYSTEM DEMONSTRATION ===");
483
484 // Creating a tracking device
485 console.log("\n--- Initializing tracking device ---");
486 const trackingDevice = new TrackingDeviceT3000("TRACK-001", "T-Rex Tracker #1");
487 trackingDevice.updateStatus("active");
488 trackingDevice.startMonitoring();
489 trackingDevice.assignDinosaur("TREX-001");
490
491 // Simulating location tracking
492 trackingDevice.updateLocation(135.42, 86.21);
493 trackingDevice.updateLocation(135.50, 86.35);
494 trackingDevice.updateLocation(135.62, 86.40);
495
496 // Creating a fence
497 console.log("\n--- Fence initialization ---");
498 const fence = new ElectricFence(
499 "FENCE-002",
500 "Fence enclosure T-Rex",
501 "T-REX-A",
502 1200, // 1200 meters long
503 15000, // 15000V
504 true // from motion sensors
505 );
506
507 // Adding permissions for an employee
508 fence.addAuthorizedPerson("EMP-047"); // Owen Grady
509
510 // Activating the fence
511 fence.updateStatus("active");
512
513 // Performing device maintenance
514 console.log("\n--- Device maintenance ---");
515 trackingDevice.scheduleMaintenance(new Date("2023-06-15"));
516 trackingDevice.performMaintenance("Battery replacement and GPS calibration", "Tech Ivan");
517
518 // Simulating an alarm
519 console.log("\n--- Simulation incident ---");
520 console.log("T-Rex is trying to break through the fence!");
521
522 // Tracking device registers rapid movement
523 trackingDevice.updateLocation(135.90, 86.70);
524 trackingDevice.updateLocation(136.10, 86.90);
525 trackingDevice.addMeasurement("acceleration", 12.5);
526
527 // Fence responds to threat
528 fence.respondToThreat("T-Rex escape attempt detected");
529
530 // Status checks after incident
531 console.log("\n--- Post-incident report ---");
532 console.log("Last dinosaur location:", trackingDevice.getLastLocation());
533 console.log("Status fence:", fence.getInfo());
534
535 // Returning to normall mode
536 console.log("\n--- Normalization situation ---");
537 fence.lowerThreatLevel();
538 fence.lowerThreatLevel();
539
540 console.log("\n=== END DEMONSTRATION ===");
541}
542
543// Running the demonstration
544demonstrateParkSystem();Résumé
Dans cet exercice, nous avons découvert les interfaces TypeScript - un outil puissant pour définir des contrats et créer des structures cohérentes dans le code.
Caractéristiques clés des interfaces :
- Définir la forme des objets - préciser quelles propriétés et méthodes les objets d'un type donné doivent avoir
- Prise en charge du polymorphisme - différentes classes peuvent implémenter la même interface de différentes manières
- Couplage faible - se concentrer sur ce que font les objets, et non sur la façon dont ils sont implémentés
- Flexibilité - propriétés optionnelles, en lecture seule, signatures d'index, etc.
- Extensibilité - les interfaces peuvent hériter d'autres interfaces
Comme le résumerait Claire Dearing : « À Jurassic Park, comme dans une bonne architecture logicielle, chacun a son rôle et ses responsabilités. Les interfaces nous permettent de définir clairement ces rôles, en garantissant que tous les systèmes coopèrent bien - sans surprises. Et crois-moi, les surprises dans un parc rempli de dinosaures sont la dernière chose dont nous avons besoin. »
Code de cette leçon : index.ts
1// Implémentation des interfaces - Parc Jurassique
2console.log("=== IMPLÉMENTATION DES INTERFACES ===\n");
3
4// ===========================================
5// 1. Définition des interfaces
6// ===========================================
7
8interface ITrackable {
9 readonly trackingId: string;
10 location: { x: number; y: number };
11 getLocation(): string;
12 updateLocation(x: number, y: number): void;
13}
14
15interface IFeedable {
16 diet: "carnivore" | "herbivore" | "omnivore";
17 lastFedAt: Date;
18 feed(food: string): string;
19 isHungry(): boolean;
20}
21
22interface IContainable {
23 enclosureId: string;
24 isContained: boolean;
25 checkContainment(): string;
26}
27
28// ===========================================
29// 2. Classe implémentant plusieurs interfaces
30// ===========================================
31
32class ParkDinosaur implements ITrackable, IFeedable, IContainable {
33 readonly trackingId: string;
34 location: { x: number; y: number };
35 diet: "carnivore" | "herbivore" | "omnivore";
36 lastFedAt: Date;
37 enclosureId: string;
38 isContained: boolean;
39 name: string;
40
41 constructor(
42 name: string,
43 diet: "carnivore" | "herbivore" | "omnivore",
44 enclosureId: string
45 ) {
46 this.name = name;
47 this.trackingId = `TRK-${Math.random().toString(36).substring(2, 8).toUpperCase()}`;
48 this.location = { x: 0, y: 0 };
49 this.diet = diet;
50 this.lastFedAt = new Date();
51 this.enclosureId = enclosureId;
52 this.isContained = true;
53 }
54
55 // ITrackable
56 getLocation(): string {
57 return `(${this.location.x}, ${this.location.y})`;
58 }
59
60 updateLocation(x: number, y: number): void {
61 this.location = { x, y };
62 console.log(` ${this.name} déplacé vers ${this.getLocation()}`);
63 }
64
65 // IFeedable
66 feed(food: string): string {
67 this.lastFedAt = new Date();
68 return `${this.name} a mangé: ${food}`;
69 }
70
71 isHungry(): boolean {
72 const hoursSinceFed = (Date.now() - this.lastFedAt.getTime()) / 3600000;
73 return hoursSinceFed > 4;
74 }
75
76 // IContainable
77 checkContainment(): string {
78 return this.isContained
79 ? `${this.name} est en sécurité dans l'enclos ${this.enclosureId}`
80 : `ALARM! ${this.name} s'est échappé de l'enclos ${this.enclosureId}!`;
81 }
82}
83
84// ===========================================
85// 3. Fonctions opérant sur les interfaces
86// ===========================================
87
88function trackEntity(entity: ITrackable): void {
89 console.log(`Suivi: ${entity.trackingId} à la position ${entity.getLocation()}`);
90}
91
92function feedEntity(entity: IFeedable): void {
93 console.log(`Diet: ${entity.diet}`);
94 console.log(`Affamé: ${entity.isHungry() ? "OUI" : "NON"}`);
95}
96
97function checkSafety(entities: IContainable[]): void {
98 console.log("\n--- Rapport de sécurité ---");
99 entities.forEach(e => console.log(e.checkContainment()));
100}
101
102// ===========================================
103// Test
104// ===========================================
105
106const velociraptor = new ParkDinosaur("Blue", "carnivore", "ENC-A7");
107const brachio = new ParkDinosaur("Patty", "herbivore", "ENC-B3");
108
109console.log("--- Suivi ---");
110trackEntity(velociraptor);
111velociraptor.updateLocation(45, 120);
112trackEntity(velociraptor);
113
114console.log("\n--- Alimentation ---");
115console.log(velociraptor.feed("Chèvre"));
116console.log(brachio.feed("Feuilles de palmier"));
117feedEntity(brachio);
118
119brachio.isContained = false; // Évasion !
120checkSafety([velociraptor, brachio]);
121
122console.log("\nInterfaces enable polymorphism and loose coupling!");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. Que fait le mot-clé implements en TypeScript ?
2. Comment définit-on une interface générique en TypeScript ?
Tâches pratiques dans le jeu
- Éditeur de code
Implémente les interfaces ITrackable et IFeedable pour les entités du parc
- Tri horizontal
Range les éléments d'une interface générique :