Cours JavaScript et TypeScript · Module 6: Bases de TypeScript

Étendre les interfaces

18 min de lecture
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éristiqueInterfaceClasse abstraite
ImplémentationDéfinition uniquement, aucune implémentationPeut contenir des implémentations de méthodes
HéritageUne classe peut implémenter plusieurs interfacesUne classe ne peut hériter que d'une seule classe abstraite
ConstructeurPas de constructeurPeut avoir un constructeur
Code au runtimeLes interfaces ne génèrent aucun code JavaScriptLes classes abstraites génèrent du code JavaScript
Modificateurs d'accèsNe peut pas utiliser de modificateurs (private, protected)Peut utiliser des modificateurs d'accès
UsageDéfinir un contratDé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é.

TechniqueUtilisation à Jurassic Park
Extension d'interface de baseHiérarchie des créatures, de la base jusqu'aux espèces spécifiques
Extension de plusieurs interfacesCréation de types hybrides, comme les prédateurs marins
Extension d'interface genericSystèmes de surveillance pour différents types de données
Fusion des déclarations d'interfacesMise à jour des protocoles de sécurité
Compatibilité structurelleTraitement 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. 1. Que se passe-t-il quand l’interface B étend l’interface A ?

  2. 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 :

Articles utiles