Cours JavaScript et TypeScript · Module 6: Bases de TypeScript
Étendre les interfaces
Dans cette leçon9
Bienvenue de nouveau à Jurassic Park ! Aujourd'hui, le Dr Henry Wu va nous guider à travers une technique avancée de TypeScript qui constitue le fondement de la construction de systèmes évolutifs et flexibles : l'extension d'interfaces. Tout comme les scientifiques du parc enrichissent les traits de base des dinosaures avec de nouvelles propriétés génétiques, en TypeScript on peut étendre des interfaces de base avec de nouvelles fonctionnalités.
Mécanisme de base de l'extension d'interfaces
Étendre une interface en TypeScript permet de créer de nouvelles interfaces à partir de celles qui existent déjà, en y ajoutant de nouvelles propriétés ou méthodes. On utilise pour cela le mot-clé extends :
1// Base interface for all creatures in the park
2interface Creature {
3 id: string;
4 name: string;
5 species: string;
6 age: number;
7 weight: number;
8}
9
10// Extending the base interface with additional properties for dinosaurs
11interface Dinosaur extends Creature {
12 originEra: string; // e.g. "Jurassic", "Cretaceous"
13 diet: "herbivore" | "carnivore" | "omnivore";
14 dangerous: boolean;
15}
16
17// Now the Dinosaur interface contains all fields from Creature plus its own
18const trex: Dinosaur = {
19 id: "TREX-01",
20 name: "Rex",
21 species: "Tyrannosaurus Rex",
22 age: 7,
23 weight: 7000,
24 originEra: "Cretaceous",
25 diet: "carnivore",
26 dangerous: true
27};Étendre plusieurs interfaces
Une interface peut étendre simultanément plusieurs autres interfaces, ce qui permet de composer des types complexes à partir de parties plus petites et spécialisées, un peu comme les scientifiques combinent l'ADN de différentes espèces pour créer des hybrides :
1// Interface defining predator abilities
2interface PredatorAbilities {
3 biteForce: number; // in PSI
4 runSpeed: number; // in km/h
5 huntingStyle: "ambush" | "chase" | "pack";
6}
7
8// Interface defining aquatic animal traits
9interface AquaticAnimal {
10 swimmingAbility: number; // 1 to 10
11 maxDepth: number; // in meters
12 saltWaterResistance: boolean;
13}
14
15// Interface for a marine predator combining both of the above interfaces
16interface MarinePredator extends PredatorAbilities, AquaticAnimal {
17 scientificName: string;
18}
19
20// Creating a dinosaur that is a marine predator
21const mosasaurus: MarinePredator & Dinosaur = {
22 // Properties from Creature
23 id: "MOSA-01",
24 name: "Mosa",
25 species: "Mosasaurus",
26 age: 15,
27 weight: 15000,
28
29 // Properties from Dinosaur
30 originEra: "Cretaceous",
31 diet: "carnivore",
32 dangerous: true,
33
34 // Properties from PredatorAbilities
35 biteForce: 18000,
36 runSpeed: 0, // doesn't run, swims
37 huntingStyle: "ambush",
38
39 // Properties from AquaticAnimal
40 swimmingAbility: 9,
41 maxDepth: 500,
42 saltWaterResistance: true,
43
44 // Properties from MarinePredator
45 scientificName: "Mosasaurus hoffmannii"
46};
47
48// Demonstration of accessing different properties
49function analyzeMarinePredator(dino: MarinePredator & Dinosaur): void {
50 console.log(`Analyzing: ${dino.name} (${dino.species})`);
51 console.log(`Bite force: ${dino.biteForce} PSI`);
52 console.log(`Can dive to depth: ${dino.maxDepth} meters`);
53 console.log(`Visitor threat: ${dino.dangerous ? "HIGH" : "LOW"}`);
54}
55
56analyzeMarinePredator(mosasaurus);Extension d'interfaces vs héritage de classes
Étendre une interface ressemble à l'héritage de classes, mais présente plusieurs différences clés. À Jurassic Park, on pourrait comparer cela à la différence entre la « description d'un dinosaure idéal » (l'interface) et un « dinosaure réel » (la classe) :
1// Interface for security systems
2interface SecuritySystem {
3 name: string;
4 securityLevel: number;
5 isActive: boolean;
6
7 activate(): void;
8 deactivate(): void;
9 test(): boolean;
10}
11
12// Class implementing the interface
13class ElectronicFence implements SecuritySystem {
14 name: string;
15 securityLevel: number;
16 isActive: boolean;
17 voltage: number; // additional property specific to this class
18
19 constructor(name: string, securityLevel: number, voltage: number) {
20 this.name = name;
21 this.securityLevel = securityLevel;
22 this.isActive = false;
23 this.voltage = voltage;
24 }
25
26 activate(): void {
27 this.isActive = true;
28 console.log(`System ${this.name} activated with voltage ${this.voltage}V`);
29 }
30
31 deactivate(): void {
32 this.isActive = false;
33 console.log(`System ${this.name} deactivated`);
34 }
35
36 test(): boolean {
37 console.log(`Testing system ${this.name}...`);
38 return Math.random() > 0.1; // 90% chance of successful test
39 }
40
41 // Method specific to this class
42 increaseVoltage(by: number): void {
43 this.voltage += by;
44 console.log(`Voltage increased to ${this.voltage}V`);
45 }
46}
47
48// Extended version of SecuritySystem for more advanced systems
49interface AdvancedSecuritySystem extends SecuritySystem {
50 backupPower: boolean;
51 videoMonitoring: boolean;
52 alarmNotifications: boolean;
53
54 runSecurityProcedure(threatLevel: number): void;
55}
56
57// Class implementing the extended interface
58class IntegratedSecuritySystem implements AdvancedSecuritySystem {
59 name: string;
60 securityLevel: number;
61 isActive: boolean;
62 backupPower: boolean;
63 videoMonitoring: boolean;
64 alarmNotifications: boolean;
65
66 constructor(name: string, securityLevel: number) {
67 this.name = name;
68 this.securityLevel = securityLevel;
69 this.isActive = false;
70 this.backupPower = true;
71 this.videoMonitoring = true;
72 this.alarmNotifications = true;
73 }
74
75 activate(): void {
76 this.isActive = true;
77 console.log(`Integrated system ${this.name} activated`);
78 }
79
80 deactivate(): void {
81 this.isActive = false;
82 console.log(`Integrated system ${this.name} deactivated`);
83 }
84
85 test(): boolean {
86 console.log(`Testing integrated system ${this.name}...`);
87 const powerTest = Math.random() > 0.05;
88 const monitoringTest = Math.random() > 0.05;
89 const notificationsTest = Math.random() > 0.05;
90
91 return powerTest && monitoringTest && notificationsTest;
92 }
93
94 runSecurityProcedure(threatLevel: number): void {
95 console.log(`Activating security procedure level ${threatLevel} for system ${this.name}`);
96 this.activate();
97
98 if (threatLevel >= 4) {
99 console.log("WARNING: High threat level! Activating all security measures!");
100 }
101 }
102}
103
104// Usage demonstration
105const trexFence = new ElectronicFence("T-Rex Enclosure", 5, 10000);
106trexFence.activate();
107trexFence.increaseVoltage(2000);
108
109const integratedParkSystem = new IntegratedSecuritySystem("Main System", 5);
110integratedParkSystem.runSecurityProcedure(4);Étendre et implémenter des interfaces generic
Les interfaces generic sont particulièrement puissantes pour modéliser des systèmes complexes, comme la surveillance de différents types de données dans le parc :
1// Generic interface for a monitoring system
2interface MonitoringSystemType<T> {
3 identifier: string;
4 dataType: string;
5 currentData: T;
6 historicalData: T[];
7
8 save(data: T): void;
9 getLatest(n: number): T[];
10 analyzeTrend(): string;
11}
12
13// Sample data type for environmental monitoring
14interface EnvironmentalData {
15 temperature: number;
16 humidity: number;
17 co2Level: number;
18 sunExposure: number;
19 pressure: number;
20 measurementTime: Date;
21}
22
23// Implementation of monitoring system for a specific data type
24class EnvironmentalMonitoring implements MonitoringSystemType<EnvironmentalData> {
25 identifier: string;
26 dataType: string;
27 currentData: EnvironmentalData;
28 historicalData: EnvironmentalData[];
29
30 constructor(identifier: string) {
31 this.identifier = identifier;
32 this.dataType = "environmental";
33 this.currentData = {
34 temperature: 0,
35 humidity: 0,
36 co2Level: 0,
37 sunExposure: 0,
38 pressure: 0,
39 measurementTime: new Date()
40 };
41 this.historicalData = [];
42 }
43
44 save(data: EnvironmentalData): void {
45 this.currentData = data;
46 this.historicalData.push(data);
47 console.log(`Saved new data for monitor ${this.identifier} at ${data.measurementTime.toISOString()}`);
48 }
49
50 getLatest(n: number): EnvironmentalData[] {
51 return this.historicalData.slice(-n);
52 }
53
54 analyzeTrend(): string {
55 if (this.historicalData.length < 2) {
56 return "Not enough data to analyze trend";
57 }
58
59 const latest = this.historicalData[this.historicalData.length - 1];
60 const previous = this.historicalData[this.historicalData.length - 2];
61
62 let trends = [];
63
64 if (latest.temperature > previous.temperature) {
65 trends.push("Temperature: RISING");
66 } else if (latest.temperature < previous.temperature) {
67 trends.push("Temperature: FALLING");
68 } else {
69 trends.push("Temperature: STABLE");
70 }
71
72 if (latest.humidity > previous.humidity) {
73 trends.push("Humidity: RISING");
74 } else if (latest.humidity < previous.humidity) {
75 trends.push("Humidity: FALLING");
76 } else {
77 trends.push("Humidity: STABLE");
78 }
79
80 return trends.join(", ");
81 }
82
83 // Method specific to environmental monitoring
84 temperatureWarning(): boolean {
85 return this.currentData.temperature > 35 || this.currentData.temperature < 15;
86 }
87}
88
89// Extending the base interface with specific functions for dinosaur monitoring
90interface DinosaurMonitoringSystem<T> extends MonitoringSystemType<T> {
91 dinosaurId: string;
92 species: string;
93 alarmThresholds: Partial<T>;
94
95 checkAlarmThresholds(): boolean;
96 sendAlertIfNeeded(): void;
97}
98
99// Data type for dinosaur health monitoring
100interface DinosaurHealthData {
101 bodyTemperature: number;
102 heartRate: number;
103 bloodPressure: [number, number]; // [systolic, diastolic]
104 stressLevel: number; // 1-10
105 activityLevel: number; // 1-10
106 measurementTime: Date;
107}
108
109// Implementation of specific monitoring system for dinosaurs
110class DinosaurHealthMonitoring implements DinosaurMonitoringSystem<DinosaurHealthData> {
111 identifier: string;
112 dataType: string;
113 currentData: DinosaurHealthData;
114 historicalData: DinosaurHealthData[];
115 dinosaurId: string;
116 species: string;
117 alarmThresholds: Partial<DinosaurHealthData>;
118
119 constructor(identifier: string, dinosaurId: string, species: string) {
120 this.identifier = identifier;
121 this.dataType = "health";
122 this.currentData = {
123 bodyTemperature: 0,
124 heartRate: 0,
125 bloodPressure: [0, 0],
126 stressLevel: 0,
127 activityLevel: 0,
128 measurementTime: new Date()
129 };
130 this.historicalData = [];
131 this.dinosaurId = dinosaurId;
132 this.species = species;
133
134 // Setting alarm thresholds based on species
135 if (species === "Tyrannosaurus") {
136 this.alarmThresholds = {
137 bodyTemperature: 39, // Above this temperature - alarm
138 heartRate: 95, // Above this heart rate - alarm
139 stressLevel: 7 // Above this stress level - alarm
140 };
141 } else if (species === "Velociraptor") {
142 this.alarmThresholds = {
143 bodyTemperature: 40,
144 heartRate: 120,
145 stressLevel: 8
146 };
147 } else {
148 // Default thresholds for other species
149 this.alarmThresholds = {
150 bodyTemperature: 38,
151 heartRate: 100,
152 stressLevel: 7
153 };
154 }
155 }
156
157 save(data: DinosaurHealthData): void {
158 this.currentData = data;
159 this.historicalData.push(data);
160 console.log(`Saved new health data for dinosaur ${this.dinosaurId} (${this.species})`);
161
162 // Check if values exceed thresholds
163 this.sendAlertIfNeeded();
164 }
165
166 getLatest(n: number): DinosaurHealthData[] {
167 return this.historicalData.slice(-n);
168 }
169
170 analyzeTrend(): string {
171 if (this.historicalData.length < 2) {
172 return "Not enough data to analyze trend";
173 }
174
175 return "Health trend analysis (sample implementation)";
176 }
177
178 checkAlarmThresholds(): boolean {
179 let thresholdExceeded = false;
180
181 if (this.alarmThresholds.bodyTemperature &&
182 this.currentData.bodyTemperature > this.alarmThresholds.bodyTemperature) {
183 console.log(`ALARM: Dinosaur ${this.dinosaurId} body temperature exceeds threshold!`);
184 thresholdExceeded = true;
185 }
186
187 if (this.alarmThresholds.heartRate &&
188 this.currentData.heartRate > this.alarmThresholds.heartRate) {
189 console.log(`ALARM: Dinosaur ${this.dinosaurId} heart rate exceeds threshold!`);
190 thresholdExceeded = true;
191 }
192
193 if (this.alarmThresholds.stressLevel &&
194 this.currentData.stressLevel > this.alarmThresholds.stressLevel) {
195 console.log(`ALARM: Dinosaur ${this.dinosaurId} stress level exceeds threshold!`);
196 thresholdExceeded = true;
197 }
198
199 return thresholdExceeded;
200 }
201
202 sendAlertIfNeeded(): void {
203 if (this.checkAlarmThresholds()) {
204 console.log(`Sending alert to veterinary team for dinosaur ${this.dinosaurId}!`);
205 }
206 }
207
208 // Method specific to dinosaur health monitoring
209 recommendIntervention(): string {
210 let recommendations = [];
211
212 if (this.currentData.bodyTemperature > this.alarmThresholds.bodyTemperature!) {
213 recommendations.push("Lower body temperature");
214 }
215
216 if (this.currentData.stressLevel > this.alarmThresholds.stressLevel!) {
217 recommendations.push("Administer sedatives");
218 }
219
220 if (this.currentData.activityLevel < 3) {
221 recommendations.push("Check energy level and possibly increase food portion");
222 }
223
224 return recommendations.length > 0
225 ? `Recommended actions: ${recommendations.join(", ")}`
226 : "No recommendations - parameters within normal range";
227 }
228}
229
230// Example usage
231const monitorPaddockTRex = new EnvironmentalMonitoring("ENV-TREX-01");
232monitorPaddockTRex.save({
233 temperature: 28.5,
234 humidity: 75,
235 co2Level: 420,
236 sunExposure: 80,
237 pressure: 1013,
238 measurementTime: new Date()
239});
240
241const monitorRexy = new DinosaurHealthMonitoring("HEALTH-TREX-01", "TREX-01", "Tyrannosaurus");
242monitorRexy.save({
243 bodyTemperature: 38.2,
244 heartRate: 72,
245 bloodPressure: [135, 85],
246 stressLevel: 3,
247 activityLevel: 4,
248 measurementTime: new Date()
249});
250
251// Simulating a health problem
252monitorRexy.save({
253 bodyTemperature: 40.1, // Fever!
254 heartRate: 98, // Elevated heart rate
255 bloodPressure: [155, 95], // High blood pressure
256 stressLevel: 8, // High stress level
257 activityLevel: 2, // Low activity
258 measurementTime: new Date()
259});
260
261console.log(monitorRexy.recommendIntervention());La fusion des déclarations d'interfaces
L'une des particularités de TypeScript est la possibilité de fusionner plusieurs déclarations d'une même interface, un peu comme les scientifiques de Jurassic Park mettent à jour leurs protocoles à mesure qu'ils obtiennent de nouvelles informations :
1// Initial definition of the security protocol
2interface SecurityProtocolType {
3 id: string;
4 name: string;
5 threatLevel: 1 | 2 | 3 | 4 | 5;
6 evacuationProcedure: string;
7}
8
9// Later extension of the same interface with additional information
10interface SecurityProtocolType {
11 responsibleStaff: string[];
12 responseTime: number; // in minutes
13}
14
15// Even later extension with communication procedures
16interface SecurityProtocolType {
17 communicationChannels: string[];
18 alarmMessage: string;
19}
20
21// Now SecurityProtocolType has all fields from all three declarations
22const protocolAlpha: SecurityProtocolType = {
23 id: "PROTO-ALFA",
24 name: "Protocol Alpha - Predator Escape",
25 threatLevel: 5,
26 evacuationProcedure: "Immediate evacuation of all public areas to safety bunkers",
27 responsibleStaff: ["Security manager", "Park manager", "Head trainer"],
28 responseTime: 5,
29 communicationChannels: ["Radio", "PA System", "Audible alarm", "SMS notifications"],
30 alarmMessage: "ATTENTION! PROTOCOL ALPHA! This is not a drill. Please immediately proceed to the nearest shelter."
31};
32
33// Function to display the protocol
34function displayProtocol(protocol: SecurityProtocolType): void {
35 console.log(`=== ${protocol.name.toUpperCase()} ===`);
36 console.log(`ID: ${protocol.id}`);
37 console.log(`Threat level: ${protocol.threatLevel}`);
38 console.log(`Response time: ${protocol.responseTime} minutes`);
39 console.log(`\nEvacuation procedure:\n${protocol.evacuationProcedure}`);
40 console.log(`\nResponsible personnel:`);
41 protocol.responsibleStaff.forEach(person => console.log(`- ${person}`));
42 console.log(`\nCommunication channels: ${protocol.communicationChannels.join(", ")}`);
43 console.log(`\nAlarm message: "${protocol.alarmMessage}"`);
44}
45
46displayProtocol(protocolAlpha);Compatibilité structurelle des interfaces
TypeScript utilise la compatibilité structurelle : cela signifie que des types sont compatibles s'ils ont la même structure, peu importe leur nom. C'est comme à Jurassic Park : si un dinosaure se comporte comme un T-Rex et ressemble à un T-Rex, du point de vue du système de sécurité, il doit être traité comme un T-Rex :
1// Definition of a predatory dinosaur interface
2interface PredatoryDinosaur {
3 id: string;
4 name: string;
5 biteForce: number;
6 runSpeed: number;
7 aggressionLevel: number;
8}
9
10// Completely separate definition, with no formal relationship to the above interface
11interface DangerousCreature {
12 id: string;
13 name: string;
14 biteForce: number;
15 runSpeed: number;
16 aggressionLevel: number;
17 // Additional field that does not interfere with compatibility
18 origin?: string;
19}
20
21function assessThreat(dino: PredatoryDinosaur): number {
22 return (dino.biteForce / 1000) + (dino.runSpeed / 10) + dino.aggressionLevel;
23}
24
25// Creating an object conforming to DangerousCreature
26const indominus: DangerousCreature = {
27 id: "IND-01",
28 name: "Indominus Rex",
29 biteForce: 15000,
30 runSpeed: 48,
31 aggressionLevel: 10,
32 origin: "Laboratory"
33};
34
35// Even though indominus is of type DangerousCreature, it can be used where
36// PredatoryDinosaur is expected, thanks to structural compatibility
37const threatLevel = assessThreat(indominus);
38console.log(`Threat level for ${indominus.name}: ${threatLevel}`);
39
40// Function expecting an object conforming to DangerousCreature
41function describeThreat(creature: DangerousCreature): string {
42 let description = `${creature.name} is an extremely dangerous creature.\n`;
43 description += `Bite force: ${creature.biteForce} PSI\n`;
44 description += `Running speed: ${creature.runSpeed} km/h\n`;
45 description += `Aggression level: ${creature.aggressionLevel}/10\n`;
46
47 if (creature.origin) {
48 description += `Created in: ${creature.origin}`;
49 }
50
51 return description;
52}
53
54// Creating an object conforming to PredatoryDinosaur
55const velociraptor: PredatoryDinosaur = {
56 id: "VEL-01",
57 name: "Blue",
58 biteForce: 4000,
59 runSpeed: 65,
60 aggressionLevel: 7
61};
62
63// We can use an object of type PredatoryDinosaur in a function expecting DangerousCreature
64// It works because velociraptor has all the required properties
65const raptorDescription = describeThreat(velociraptor);
66console.log(raptorDescription);Interfaces vs types abstraits
Comparons les interfaces avec les types abstraits (classes abstraites) dans le contexte de Jurassic Park :
1// Interface for security systems
2interface SecuritySystemInterface {
3 name: string;
4 activate(): void;
5 deactivate(): void;
6 test(): boolean;
7}
8
9// Abstract class for security systems
10abstract class SecuritySystemAbstractClass {
11 constructor(public name: string, protected securityLevel: number) {}
12
13 abstract activate(): void;
14 abstract deactivate(): void;
15
16 // Shared implementation for all derived classes
17 test(): boolean {
18 console.log(`Testing system ${this.name}...`);
19 return Math.random() > 0.1; // 90% chance of success
20 }
21
22 // Method available to all derived classes
23 getLevel(): number {
24 return this.securityLevel;
25 }
26}
27
28// Interface implementation
29class MotionScanners implements SecuritySystemInterface {
30 constructor(public name: string) {}
31
32 activate(): void {
33 console.log(`Motion scanners ${this.name} activated`);
34 }
35
36 deactivate(): void {
37 console.log(`Motion scanners ${this.name} deactivated`);
38 }
39
40 test(): boolean {
41 console.log(`Testing motion scanners ${this.name}...`);
42 return true;
43 }
44}
45
46// Extending the abstract class
47class ElectricFence extends SecuritySystemAbstractClass {
48 private voltage: number;
49
50 constructor(name: string, securityLevel: number, voltage: number) {
51 super(name, securityLevel);
52 this.voltage = voltage;
53 }
54
55 activate(): void {
56 console.log(`Electric fence ${this.name} activated with voltage ${this.voltage}V`);
57 }
58
59 deactivate(): void {
60 console.log(`Electric fence ${this.name} deactivated`);
61 }
62
63 increaseVoltage(o: number): void {
64 this.voltage += o;
65 console.log(`Voltage of fence ${this.name} increased to ${this.voltage}V`);
66 }
67}
68
69// Using both approaches
70const scanners = new MotionScanners("T-Rex Zone");
71scanners.activate();
72scanners.test();
73
74const fence = new ElectricFence("Eastern paddock", 4, 10000);
75fence.activate();
76fence.increaseVoltage(2000);
77console.log(`Fence security level: ${fence.getLevel()}`);Différences clés :
| Caractéristique | Interface | Classe abstraite |
|---|---|---|
| Implémentation | Définition uniquement, aucune implémentation | Peut contenir des implémentations de méthodes |
| Héritage | Une classe peut implémenter plusieurs interfaces | Une classe ne peut hériter que d'une seule classe abstraite |
| Constructeur | Pas de constructeur | Peut avoir un constructeur |
| Code au runtime | Les interfaces ne génèrent aucun code JavaScript | Les classes abstraites génèrent du code JavaScript |
| Modificateurs d'accès | Ne peut pas utiliser de modificateurs (private, protected) | Peut utiliser des modificateurs d'accès |
| Usage | Définir un contrat | Définir des fonctionnalités de base avec une implémentation partielle |
Application pratique de l'extension d'interfaces
Voyons maintenant un exemple complet illustrant comment l'extension d'interfaces peut être utilisée dans le système de gestion de Jurassic Park :
1// Base interface for all entities in the park
2interface JurassicParkBase {
3 id: string;
4 name: string;
5 dateAdded: Date;
6 lastUpdated: Date;
7}
8
9// Extension for living creatures
10interface ParkLivingCreatures extends JurassicParkBase {
11 age: number;
12 gender: "male" | "female";
13 healthStatus: "excellent" | "good" | "needs attention" | "sick" | "critical";
14 healthHistory: {
15 date: Date;
16 description: string;
17 procedure?: string;
18 }[];
19}
20
21// Extension for dinosaurs
22interface ParkDinosaur extends ParkLivingCreatures {
23 species: string;
24 originEra: string;
25 diet: "herbivore" | "carnivore" | "omnivore";
26 dangerLevel: 1 | 2 | 3 | 4 | 5;
27 location: string; // Enclosure ID
28 traits: {
29 height: number;
30 length: number;
31 weight: number;
32 maxSpeed: number;
33 biteForce?: number; // Only for predators
34 };
35}
36
37// Extension for employees
38interface ParkEmployee extends ParkLivingCreatures {
39 firstName: string;
40 lastName: string;
41 position: string;
42 accessLevel: 1 | 2 | 3 | 4 | 5;
43 departments: string[];
44 address: {
45 street: string;
46 city: string;
47 postalCode: string;
48 country: string;
49 };
50 contact: {
51 phone: string;
52 email: string;
53 };
54}
55
56// Interface for park locations
57interface ParkLocation extends JurassicParkBase {
58 type: "enclosure" | "building" | "attraction" | "laboratory" | "public zone";
59 coordinates: {
60 width: number;
61 length: number;
62 };
63 area: number; // in square meters
64 capacity: number;
65 accessLevel: 1 | 2 | 3 | 4 | 5;
66 securitySystems: string[]; // List of security system IDs
67 status: "active" | "inactive" | "under maintenance" | "under construction";
68}
69
70// Interface for dinosaur enclosures
71interface DinosaurEnclosure extends ParkLocation {
72 residents: string[]; // List of dinosaur IDs
73 environmentType: "forest" | "savanna" | "swamp" | "mountains" | "coast" | "water";
74 specialRequirements?: string[];
75 traits: {
76 fenceHeight: number;
77 fenceVoltage?: number; // For predators
78 scannersCount: number;
79 cameraCount: number;
80 };
81}
82
83// Interface for laboratories
84interface ParkLaboratory extends ParkLocation {
85 specializations: "genetics" | "behavioral" | "veterinary" | "microbiology";
86 equipment: string[];
87 staff: string[]; // List of employee IDs
88 researchProjects: {
89 id: string;
90 name: string;
91 start: Date;
92 end?: Date;
93 status: "planned" | "in progress" | "completed" | "suspended";
94 }[];
95}
96
97// Park management system
98class JurassicParkSystem {
99 private dinosaurs: Map<string, ParkDinosaur> = new Map();
100 private employees: Map<string, ParkEmployee> = new Map();
101 private locations: Map<string, ParkLocation> = new Map();
102 private laboratories: Map<string, ParkLaboratory> = new Map();
103 private enclosures: Map<string, DinosaurEnclosure> = new Map();
104
105 // Methods for dinosaurs
106 addDinosaur(dino: ParkDinosaur): void {
107 this.dinosaurs.set(dino.id, dino);
108 console.log(`Added dinosaur: ${dino.name} (${dino.species})`);
109
110 // Update enclosure
111 if (dino.location && this.enclosures.has(dino.location)) {
112 const enclosure = this.enclosures.get(dino.location)!;
113 enclosure.residents.push(dino.id);
114 this.enclosures.set(enclosure.id, enclosure);
115 }
116 }
117
118 updateDinosaurHealthStatus(id: string, newStatus: ParkDinosaur["healthStatus"], description: string): void {
119 if (!this.dinosaurs.has(id)) {
120 console.error(`Dinosaur with ID ${id} does not exist`);
121 return;
122 }
123
124 const dino = this.dinosaurs.get(id)!;
125 const oldStatus = dino.healthStatus;
126 dino.healthStatus = newStatus;
127 dino.lastUpdated = new Date();
128
129 // Add entry to health history
130 dino.healthHistory.push({
131 date: new Date(),
132 description: `Health status changed from "${oldStatus}" to "${newStatus}". ${description}`
133 });
134
135 this.dinosaurs.set(id, dino);
136 console.log(`Updated health status of dinosaur ${dino.name} to: ${newStatus}`);
137 }
138
139 // Methods for employees
140 addEmployee(employee: ParkEmployee): void {
141 this.employees.set(employee.id, employee);
142 console.log(`Added employee: ${employee.firstName} ${employee.lastName} (${employee.position})`);
143 }
144
145 // Methods for locations
146 addEnclosure(enclosure: DinosaurEnclosure): void {
147 this.enclosures.set(enclosure.id, enclosure);
148 this.locations.set(enclosure.id, enclosure);
149 console.log(`Added enclosure: ${enclosure.name}`);
150 }
151
152 addLaboratory(lab: ParkLaboratory): void {
153 this.laboratories.set(lab.id, lab);
154 this.locations.set(lab.id, lab);
155 console.log(`Added laboratory: ${lab.name} (${lab.specializations})`);
156 }
157
158 // Reporting methods
159 generateJurassicParkReport(): string {
160 const report = [];
161 report.push("=== JURASSIC PARK STATUS REPORT ===");
162 report.push(`Report date: ${new Date().toLocaleString()}`);
163 report.push(`\nGeneral statistics:`);
164 report.push(`- Number of dinosaurs: ${this.dinosaurs.size}`);
165 report.push(`- Number of employees: ${this.employees.size}`);
166 report.push(`- Number of locations: ${this.locations.size}`);
167 report.push(` - Enclosures: ${this.enclosures.size}`);
168 report.push(` - Laboratories: ${this.laboratories.size}`);
169
170 // Dinosaur statistics
171 const healthStatistics = {
172 "excellent": 0,
173 "good": 0,
174 "needs attention": 0,
175 "sick": 0,
176 "critical": 0
177 };
178
179 let predatorsCount = 0;
180
181 for (const dino of this.dinosaurs.values()) {
182 healthStatistics[dino.healthStatus]++;
183 if (dino.diet === "carnivore") {
184 predatorsCount++;
185 }
186 }
187
188 report.push(`\nDinosaur health statistics:`);
189 for (const [status, count] of Object.entries(healthStatistics)) {
190 report.push(`- ${status}: ${count}`);
191 }
192
193 report.push(`Predators: ${predatorsCount}`);
194 report.push(`Herbivores: ${this.dinosaurs.size - predatorsCount}`);
195
196 // Enclosure statistics
197 const enclosuresByStatus = {
198 "active": 0,
199 "inactive": 0,
200 "under maintenance": 0,
201 "under construction": 0
202 };
203
204 for (const enclosure of this.enclosures.values()) {
205 enclosuresByStatus[enclosure.status]++;
206 }
207
208 report.push(`\nEnclosure statistics:`);
209 for (const [status, count] of Object.entries(enclosuresByStatus)) {
210 report.push(`- ${status}: ${count}`);
211 }
212
213 return report.join("\n");
214 }
215}
216
217// Example usage
218const park = new JurassicParkSystem();
219
220// Adding an enclosure
221park.addEnclosure({
222 id: "WYB-001",
223 name: "T-Rex Paddock",
224 dateAdded: new Date(2020, 0, 15),
225 lastUpdated: new Date(),
226 type: "enclosure",
227 coordinates: {
228 width: 20.123,
229 length: -75.456
230 },
231 area: 50000, // 5 hectares
232 capacity: 2,
233 accessLevel: 5,
234 securitySystems: ["SEC-001", "SEC-002", "SEC-003"],
235 status: "active",
236 residents: [],
237 environmentType: "forest",
238 traits: {
239 fenceHeight: 8,
240 fenceVoltage: 10000,
241 scannersCount: 24,
242 cameraCount: 36
243 }
244});
245
246// Adding a laboratory
247park.addLaboratory({
248 id: "LAB-001",
249 name: "Dr. Wu's Genetics Laboratory",
250 dateAdded: new Date(2018, 5, 10),
251 lastUpdated: new Date(),
252 type: "laboratory",
253 coordinates: {
254 width: 20.124,
255 length: -75.458
256 },
257 area: 2000,
258 capacity: 30,
259 accessLevel: 4,
260 securitySystems: ["SEC-101", "SEC-102"],
261 status: "active",
262 specializations: "genetics",
263 equipment: ["DNA sequencer", "Sample isolators", "Cryogenic chamber"],
264 staff: [],
265 researchProjects: [
266 {
267 id: "PROJ-001",
268 name: "Strengthening resistance genes in Triceratops",
269 start: new Date(2022, 3, 15),
270 status: "in progress"
271 }
272 ]
273});
274
275// Adding a dinosaur
276park.addDinosaur({
277 id: "DINO-001",
278 name: "Rexy",
279 dateAdded: new Date(2020, 5, 20),
280 lastUpdated: new Date(),
281 age: 7,
282 gender: "female",
283 healthStatus: "good",
284 healthHistory: [
285 {
286 date: new Date(2020, 5, 20),
287 description: "Initial examination. All parameters within normal range."
288 },
289 {
290 date: new Date(2021, 2, 15),
291 description: "Routine examination. Slightly elevated triglycerides.",
292 procedure: "Diet change to one richer in Omega-3 fatty acids"
293 }
294 ],
295 species: "Tyrannosaurus Rex",
296 originEra: "Late Cretaceous",
297 diet: "carnivore",
298 dangerLevel: 5,
299 location: "WYB-001",
300 traits: {
301 height: 4.5,
302 length: 12,
303 weight: 7200,
304 maxSpeed: 32,
305 biteForce: 12800
306 }
307});
308
309// Adding an employee
310park.addEmployee({
311 id: "EMP-001",
312 name: "Employee",
313 dateAdded: new Date(2019, 1, 10),
314 lastUpdated: new Date(),
315 age: 42,
316 gender: "male",
317 healthStatus: "excellent",
318 healthHistory: [
319 {
320 date: new Date(2022, 1, 5),
321 description: "Routine periodic examination. All parameters within normal range."
322 }
323 ],
324 firstName: "Owen",
325 lastName: "Grady",
326 position: "Raptor Trainer",
327 accessLevel: 4,
328 departments: ["Behavioral Research", "Animal Care"],
329 address: {
330 street: "Bungalow 12",
331 city: "Isla Nublar",
332 postalCode: "00000",
333 country: "Costa Rica"
334 },
335 contact: {
336 phone: "+1-555-123-4567",
337 email: "owen.grady@jurassicworld.com"
338 }
339});
340
341// Updating dinosaur health status
342park.updateDinosaurHealthStatus(
343 "DINO-001",
344 "needs attention",
345 "Noticed reduced appetite and slight fever."
346);
347
348// Generating report
349console.log(park.generateJurassicParkReport());Résumé
L'extension d'interfaces en TypeScript est une technique puissante qui permet de modéliser des hiérarchies de types complexes ainsi que les relations qui les unissent. Dans le contexte de Jurassic Park, elle nous permet de modéliser précisément diverses entités et divers systèmes, des dinosaures et des employés jusqu'aux systèmes avancés de surveillance et de sécurité.
| Technique | Utilisation à Jurassic Park |
|---|---|
| Extension d'interface de base | Hiérarchie des créatures, de la base jusqu'aux espèces spécifiques |
| Extension de plusieurs interfaces | Création de types hybrides, comme les prédateurs marins |
| Extension d'interface generic | Systèmes de surveillance pour différents types de données |
| Fusion des déclarations d'interfaces | Mise à jour des protocoles de sécurité |
| Compatibilité structurelle | Traitement uniforme des différents types de dinosaures par les systèmes de sécurité |
Le Dr Wu résume ainsi : « Étendre des interfaces en TypeScript, c'est un peu comme notre travail sur de nouvelles espèces de dinosaures. On part d'une structure de base et on l'enrichit de nouveaux traits et de nouvelles fonctionnalités, créant ainsi des entités toujours plus spécialisées et complexes. Cette flexibilité et cette capacité à réutiliser des définitions existantes nous permettent de créer des systèmes cohérents tout en restant extrêmement variés : c'est essentiel, aussi bien en génétique qu'en programmation. »
Code de cette leçon : index.ts
1// Extending interfaces in Jurassic Park
2console.log("Extending TypeScript interfaces\n");
3
4// ===========================================
5// 1. Extension de base
6// ===========================================
7console.log("=== EXTENSION DE BASE ===");
8
9// Interface de base
10interface Animal {
11 name: string;
12 age: number;
13 weight: number;
14}
15
16// Extension de l'interface
17interface Dinosaur extends Animal {
18 species: string;
19 period: "Triassic" | "Jurassic" | "Cretaceous";
20 diet: "carnivore" | "herbivore" | "omnivore";
21}
22
23const trex: Dinosaur = {
24 name: "Rexy",
25 age: 5,
26 weight: 8000,
27 species: "Tyrannosaurus Rex",
28 period: "Cretaceous",
29 diet: "carnivore"
30};
31
32console.log("T-Rex:", trex);
33
34// ===========================================
35// 2. Extension multiple
36// ===========================================
37console.log("\n=== EXTENSION MULTIPLE ===");
38
39interface HasName {
40 name: string;
41}
42
43interface HasWeight {
44 weight: number;
45}
46
47interface HasDiet {
48 diet: "carnivore" | "herbivore" | "omnivore";
49}
50
51// Extending multiple interfaces
52interface CompleteDinosaur extends HasName, HasWeight, HasDiet {
53 species: string;
54 dangerLevel: number;
55}
56
57const velociraptor: CompleteDinosaur = {
58 name: "Blue",
59 weight: 350,
60 diet: "carnivore",
61 species: "Velociraptor",
62 dangerLevel: 8
63};
64
65console.log("Velociraptor:", velociraptor);
66
67// ===========================================
68// 3. Property overriding
69// ===========================================
70console.log("\n=== PROPERTY OVERRIDING ===");
71
72interface BasicDinosaur {
73 id: string | number;
74 name: string;
75}
76
77// Narrowing type when extending
78interface SpecificDinosaur extends BasicDinosaur {
79 id: string; // narrows type de string | number vers string
80 trackingCode: string;
81}
82
83const trackedDino: SpecificDinosaur = {
84 id: "DINO-001", // doit be string
85 name: "Rex",
86 trackingCode: "TR-XYZ-123"
87};
88
89console.log("Tracked dinosaur:", trackedDino);
90
91// ===========================================
92// 4. Extension avec des méthodes supplémentaires
93// ===========================================
94console.log("\n=== EXTENSION AVEC DES MÉTHODES ===");
95
96interface LivingBeing {
97 name: string;
98 eat(amount: number): void;
99 sleep(hours: number): void;
100}
101
102interface Predator extends LivingBeing {
103 hunt(prey: string): string;
104 roar(): string;
105 attackPower: number;
106}
107
108class TyrannosaurusRex implements Predator {
109 name: string;
110 attackPower: number;
111 energy: number = 100;
112
113 constructor(name: string) {
114 this.name = name;
115 this.attackPower = 95;
116 }
117
118 eat(amount: number): void {
119 this.energy += amount * 2;
120 if (this.energy > 100) this.energy = 100;
121 console.log(`${this.name} ate ${amount}kg. Energy: ${this.energy}`);
122 }
123
124 sleep(hours: number): void {
125 this.energy += hours * 10;
126 if (this.energy > 100) this.energy = 100;
127 console.log(`${this.name} slept ${hours}h. Energy: ${this.energy}`);
128 }
129
130 hunt(prey: string): string {
131 this.energy -= 20;
132 return `${this.name} hunts ${prey}! Energy: ${this.energy}`;
133 }
134
135 roar(): string {
136 this.energy -= 5;
137 return `${this.name} roars: ROAAAAAR! (${this.attackPower} power)`;
138 }
139}
140
141const rexy = new TyrannosaurusRex("Rexy");
142console.log(rexy.roar());
143console.log(rexy.hunt("Triceratops"));
144rexy.eat(50);
145
146// ===========================================
147// 5. Chain extending
148// ===========================================
149console.log("\n=== CHAIN EXTENDING ===");
150
151interface Entity {
152 id: string;
153}
154
155interface LivingEntity extends Entity {
156 isAlive: boolean;
157}
158
159interface BreathingEntity extends LivingEntity {
160 oxygenLevel: number;
161}
162
163interface IntelligentEntity extends BreathingEntity {
164 intelligenceLevel: number;
165 canCommunicate: boolean;
166}
167
168const velociRaptor: IntelligentEntity = {
169 id: "VR-001",
170 isAlive: true,
171 oxygenLevel: 98,
172 intelligenceLevel: 85,
173 canCommunicate: true
174};
175
176console.log("Intelligent entity:", velociRaptor);
177
178// ===========================================
179// 6. Generics avec extension
180// ===========================================
181console.log("\n=== GENERICS AVEC EXTENSION ===");
182
183interface Trackable {
184 trackingId: string;
185 lastSeen: Date;
186}
187
188interface DataEntry extends Trackable {
189 data: any;
190}
191
192interface DinosaurEntry<T> extends DataEntry {
193 dinosaurData: T;
194}
195
196interface TRexData {
197 species: "Tyrannosaurus Rex";
198 teethCount: number;
199 biteForce: number;
200}
201
202const trexEntry: DinosaurEntry<TRexData> = {
203 trackingId: "TRK-001",
204 lastSeen: new Date(),
205 data: { notes: "Healthy specimen" },
206 dinosaurData: {
207 species: "Tyrannosaurus Rex",
208 teethCount: 60,
209 biteForce: 12800
210 }
211};
212
213console.log("T-Rex entry:", trexEntry);
214
215// ===========================================
216// 7. Extension avec Index Signatures
217// ===========================================
218console.log("\n=== INDEX SIGNATURES ===");
219
220interface BaseConfig {
221 version: string;
222 enabled: boolean;
223}
224
225interface ExtendedConfig extends BaseConfig {
226 [key: string]: any; // additional dynamic properties
227}
228
229const parkConfig: ExtendedConfig = {
230 version: "2.0",
231 enabled: true,
232 maxCapacity: 10000,
233 securityLevel: "high",
234 maintenanceMode: false
235};
236
237console.log("Park config:", parkConfig);
238
239// ===========================================
240// 8. Extension vs Intersection
241// ===========================================
242console.log("\n=== EXTENDS VS INTERSECTION ===");
243
244// Extends (interfaces)
245interface A {
246 a: string;
247}
248
249interface B extends A {
250 b: number;
251}
252
253const objB: B = {
254 a: "value",
255 b: 123
256};
257
258// Intersection (types)
259type C = {
260 c: boolean;
261};
262
263type D = C & {
264 d: string[];
265};
266
267const objD: D = {
268 c: true,
269 d: ["item1", "item2"]
270};
271
272console.log("Interface extension:", objB);
273console.log("Type intersection:", objD);
274
275// ===========================================
276// 9. Example pratique - Park System
277// ===========================================
278console.log("\n=== SYSTÈME DU PARK ===");
279
280interface Identifiable {
281 id: string;
282}
283
284interface Timestamped {
285 createdAt: Date;
286 updatedAt: Date;
287}
288
289interface Locatable {
290 latitude: number;
291 longitude: number;
292}
293
294interface ParkEntity extends Identifiable, Timestamped, Locatable {
295 name: string;
296 status: "active" | "inactive" | "maintenance";
297}
298
299interface Paddock extends ParkEntity {
300 capacity: number;
301 currentOccupancy: number;
302 securityLevel: "low" | "medium" | "high" | "critical";
303}
304
305interface DinosaurPark extends ParkEntity {
306 paddocks: Paddock[];
307 totalDinosaurs: number;
308 isOpen: boolean;
309}
310
311const jurassicPark: DinosaurPark = {
312 id: "PARK-001",
313 createdAt: new Date("1993-06-11"),
314 updatedAt: new Date(),
315 latitude: 22.3964,
316 longitude: -159.5261,
317 name: "Jurassic Park",
318 status: "active",
319 paddocks: [],
320 totalDinosaurs: 0,
321 isOpen: false
322};
323
324console.log("Jurassic Park:", jurassicPark);
325
326// ===========================================
327// 10. Résumé
328// ===========================================
329console.log("\n=== RÉSUMÉ ===");
330console.log("extends - extending interfaces");
331console.log("Extension multiple - inheritance of multiple interfaces");
332console.log("Écrasement - type narrowing");
333console.log("Méthodes - adding functionality");
334console.log("Chaînage - building hierarchy");
335console.log("Generics - extension paramétrique");
336
337console.log("\nExtending interfaces mastered!");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 se passe-t-il quand l’interface B étend l’interface A ?
2. Une interface en TypeScript peut-elle étendre plusieurs interfaces à la fois ?
Tâches pratiques dans le jeu
- Éditeur de code
Crée une interface de base Animal et étends-la en Dinosaur avec les champs supplémentaires era et diet.
- Tri horizontal
Ordonne les éléments de l’extension d’interface en TypeScript :