"In Jurassic Park, everything must operate according to strict protocols," explains Claire Dearing, the operations director. "Every employee has a clearly defined scope of duties, and every system must meet specific requirements. We don't care how exactly a guard watches the raptor enclosure - what matters is that they do their job according to security procedures."
In the TypeScript world, interfaces play a similar role - they define a "contract" that classes must fulfill, without specifying how exactly they should do it. In this exercise, you will learn how to design and implement interfaces by creating an advanced management system for Jurassic Park.
An interface in TypeScript is a structure that defines the shape of an object - what properties and methods it should have, without specifying their implementation. You can think of an interface as a contract or protocol that a class must follow.
Let's look at the basic interface syntax:
1// Interface definition
2interface PracownikParku {
3 // Properties
4 id: string;
5 firstName: string;
6 lastName: string;
7 accessLevel: number;
8
9 // Methods
10 zaloguj(): void;
11 wyloguj(): void;
12 checkPermissions(requiredLevel: number): boolean;
13}
14
15// Class implementing the interface
16class GuardParku implements PracownikParku {
17 // Implementation of all required properties
18 id: string;
19 firstName: string;
20 lastName: string;
21 accessLevel: number;
22 private aktywnyStatus: 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; // Standardowy poziom dla guardy
29 }
30
31 // Implementation of all required methods
32 zaloguj(): void {
33 console.log(`Guard ${this.firstName} ${this.lastName} logged into the system.`);
34 this.aktywnyStatus = true;
35 }
36
37 wyloguj(): void {
38 console.log(`Guard ${this.firstName} ${this.lastName} logged out of the system.`);
39 this.aktywnyStatus = false;
40 }
41
42 checkPermissions(requiredLevel: number): boolean {
43 return this.accessLevel >= requiredLevel;
44 }
45
46 // Additional methods specific to the guard
47 patrolujSektor(sektorId: string): void {
48 if (!this.aktywnyStatus) {
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 ${sektorId}.`);
54 }
55}
56
57// Another class implementing the same interface
58class ScientistParku implements PracownikParku {
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 zaloguj(): void {
74 console.log(`Dr ${this.firstName} ${this.lastName} logged into the laboratory system.`);
75 }
76
77 wyloguj(): 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 === "genetyka") {
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", "genetyka");
98
99// Both classes implement the same interface, so we can use the same methods
100guard.zaloguj();
101scientist.zaloguj();
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.patrolujSektor("B-5");
109scientist.conductResearch("DNA Velociraptora");In the example above:
PracownikParku interface specifying what properties and methods every employee must haveTypeScript interfaces allow for more advanced property definitions:
1interface SecuritySystem {
2 // Required property
3 id: string;
4
5 // Read-only property - cannot be modified after initialization
6 readonly systemType: "monitoring" | "ogrodzenie" | "czujnikRuchu" | "alarm";
7
8 // Optional property - may or may not exist
9 advancedOptions?: Record<string, any>;
10
11 // Methods
12 aktywuj(): void;
13 deaktywuj(): void;
14 checkStatus(): string;
15}
16
17class FenceSystem implements SecuritySystem {
18 id: string;
19 readonly systemType: "monitoring" | "ogrodzenie" | "czujnikRuchu" | "alarm" = "ogrodzenie";
20 advancedOptions?: Record<string, any>; // Optional property
21
22 private voltage: number = 0;
23 private aktywny: 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 aktywuj(): void {
33 this.aktywny = true;
34 this.voltage = this.advancedOptions?.startingVoltage || 10000; // Using optional property
35 console.log(`System ogrodzenia ${this.id} activated. Voltage: ${this.voltage}V`);
36 }
37
38 deaktywuj(): void {
39 this.aktywny = false;
40 this.voltage = 0;
41 console.log(`System ogrodzenia ${this.id} deaktywowany.`);
42 }
43
44 checkStatus(): string {
45 return `System ogrodzenia ${this.id}: ${this.aktywny ? "ACTIVE" : "INACTIVE"}, Voltage: ${this.voltage}V`;
46 }
47
48 // Custom method, not required by the interface
49 increaseVoltage(increment: number): void {
50 if (!this.aktywny) {
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 automatyczneRestarty: true
64});
65
66const ogrodzenieStandardowe = new FenceSystem("FNC-ST1");
67
68tRexFence.aktywuj(); // Uses custom starting voltage
69ogrodzenieStandardowe.aktywuj(); // Uses default starting voltage
70
71// Error: Cannot assign to 'systemType' because it is a read-only property
72// tRexFence.systemType = "alarm";In this example:
readonly property that cannot be modified after initializationadvancedOptions (marked with ?)Interfaces, like classes, can extend other interfaces, inheriting their properties and methods:
1// Basic interface for all animals
2interface Animal {
3 id: string;
4 gatunek: string;
5 weight: number;
6 karma(): void;
7 monitoruj(): string;
8}
9
10// Extended interface specific to dinosaurs
11interface Dinosaur extends Animal {
12 dangerLevel: number;
13 eraDinosaura: "trias" | "jura" | "kreda";
14 getSpecialDiet(): string;
15}
16
17// Even more specialized interface
18interface PredatoryDinosaur extends Dinosaur {
19 biteForce: number;
20 speed: number;
21 poluj(): void;
22}
23
24// Implementation
25class TyrannosaurusRex implements PredatoryDinosaur {
26 // Properties from Animal interface
27 id: string;
28 gatunek: string = "Tyrannosaurus Rex";
29 weight: number;
30
31 // Properties from Dinosaur interface
32 dangerLevel: number = 10;
33 eraDinosaura: "trias" | "jura" | "kreda" = "kreda";
34
35 // Properties from PredatoryDinosaur interface
36 biteForce: number;
37 speed: number;
38
39 // Custom properties
40 private wiek: number;
41 private ostatnieKarmienie: Date | null = null;
42
43 constructor(id: string, weight: number, wiek: number, biteForce: number, speed: number) {
44 this.id = id;
45 this.weight = weight;
46 this.wiek = wiek;
47 this.biteForce = biteForce;
48 this.speed = speed;
49 }
50
51 // Implementation of methods from Animal interface
52 karma(): void {
53 this.ostatnieKarmienie = new Date();
54 console.log(`T-Rex ${this.id} was fed.`);
55 }
56
57 monitoruj(): string {
58 const statusKarmienia = this.ostatnieKarmienie
59 ? `Ostatnie karmienie: ${this.ostatnieKarmienie.toLocaleString()}`
60 : "Brak informacji o karmieniu";
61
62 return `T-Rex ${this.id}: Weight: ${this.weight}kg, Age: ${this.wiek} lat. ${statusKarmienia}`;
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 poluj(): 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.monitoruj());
80console.log(`Special diet: ${rex.getSpecialDiet()}`);
81rex.poluj();In this example we created a hierarchy of interfaces:
Animal - the base interface for all animalsDinosaur - extends Animal with properties specific to dinosaursPredatoryDinosaur - extends Dinosaur with properties specific to predatorsThe
TyrannosaurusRex class implements the most specialized interface PredatoryDinosaur, which means it must provide all properties and methods from all three interfaces.TypeScript also offers type aliases (
type), which have similar capabilities to interfaces, but there are some differences between them:1// Interface
2interface PomiarWybieg {
3 width: number;
4 length: number;
5 calculateArea(): number;
6}
7
8// Type (Type alias)
9type PomiarKlatka = {
10 width: number;
11 height: number;
12 depth: number;
13 calculateVolume(): number;
14};
15
16// Extending an interface
17interface EnclosureMeasurementWithFence extends PomiarWybieg {
18 fenceHeight: number;
19 fenceMaterial: string;
20}
21
22// "Extending" a type through intersection
23type PomiarKlatkaWzmocniona = PomiarKlatka & {
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 KlatkaTransportowa implements PomiarKlatkaWzmocniona {
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}Main differences between interfaces and types:
Extensibility:
1// Interface can be extended with additional declarations
2interface DiagnozaDinosaura {
3 wynikBadania: string;
4}
5
6// Adding new properties to an existing interface
7interface DiagnozaDinosaura {
8 dataWykonania: Date;
9}
10
11// Now DiagnozaDinosaura has both properties!
12
13// For types this won't work:
14type OpisWybieg = {
15 id: string;
16};
17
18// Error: Duplicate identifier 'OpisWybieg'
19// type OpisWybieg = {
20// nazwa: string;
21// };Combining:
extends&Advanced constructs:
1// Advanced types that cannot be expressed with interfaces
2type StatusDinosaura = "zdrowy" | "chory" | "w trakcie leczenia" | "poddany kwarantannie";
3
4type IDDinosaura = string | number;
5
6type AnimalOrPlant = Animal | { genus: string, height: number };
7
8// Mapped types
9type Opcjonalny<T> = {
10 [P in keyof T]?: T[P];
11};
12
13// Using a mapped type with an interface
14type OpcjonalnyDinosaur = Opcjonalny<Dinosaur>;Interfaces can also define the shape of functions and class constructors:
1// Interface for a function
2interface PhotoProcessing {
3 (path: string, options?: { resolution?: number, filtr?: 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(`Opcje: ${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(wynik => console.log(`Przetworzono: ${wynik}`));
20
21// Interface for a class constructor
22interface DinosaurConstructor {
23 new (id: string, gatunek: string, wiek: number): Animal;
24}
25
26// Function using a constructor
27function createDinosaur(Constructor: DinosaurConstructor, id: string, gatunek: string, wiek: number): Animal {
28 return new Constructor(id, gatunek, wiek);
29}
30
31// Class conforming to the constructor interface
32class DinosaurPrototype implements Animal {
33 id: string;
34 gatunek: string;
35 weight: number = 0;
36 private _wiek: number;
37
38 constructor(id: string, gatunek: string, wiek: number) {
39 this.id = id;
40 this.gatunek = gatunek;
41 this._age = age;
42
43 // Weight calculation based on age
44 this.weight = wiek * 100; // Simplified calculation
45 }
46
47 karma(): void {
48 console.log(`Karmienie prototypu ${this.gatunek}.`);
49 }
50
51 monitoruj(): string {
52 return `Monitoring prototypu ${this.gatunek} (ID: ${this.id}).`;
53 }
54}
55
56// Using the factory function
57const nowyDinosaur = createDinosaur(DinosaurPrototype, "PROTO-1", "Testosaurus", 5);
58console.log(nowyDinosaur.monitoruj());Interfaces can also define indexed types that allow dynamic access to properties:
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 gatunek: "Triceratops",
42 weight: 6000,
43 karma(): void { console.log("Karmienie Triceratopsa"); },
44 monitoruj(): string { return "Monitoring Triceratopsa"; }
45};
46
47const stegosaurus = {
48 id: "STEG-01",
49 gatunek: "Stegosaurus",
50 weight: 5000,
51 karma(): void { console.log("Karmienie Stegozaura"); },
52 monitoruj(): string { return "Monitoring Stegozaura"; }
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.gatunek}`);
62 animal.karma();
63}
64
65console.log(`Number of animals in repository: ${animalRepo.animalCount}`);
66console.log(`Last update: ${animalRepo.lastUpdate.toLocaleString()}`);A class can implement multiple interfaces simultaneously:
1// Interface for monitoring capability
2interface Monitorable {
3 rozpocznijMonitoring(): void;
4 stopMonitoring(): void;
5 getMonitoringStatus(): string;
6}
7
8// Interface for alarm systems
9interface AlarmSystem {
10 uruchomAlarm(): void;
11 disableAlarm(): void;
12 czyAlarmActive: 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 aktywnyMonitoring: boolean = false;
26 czyAlarmActive: 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 rozpocznijMonitoring(): void {
37 this.aktywnyMonitoring = true;
38 console.log(`System ${this.id}: Monitoring started.`);
39 }
40
41 stopMonitoring(): void {
42 this.aktywnyMonitoring = false;
43 console.log(`System ${this.id}: Monitoring stopped.`);
44 }
45
46 getMonitoringStatus(): string {
47 return `System ${this.id}: Monitoring ${this.aktywnyMonitoring ? "aktywny" : "nieaktywny"}`;
48 }
49
50 // Implementation of AlarmSystem
51 uruchomAlarm(): void {
52 this.czyAlarmActive = true;
53 this.blockAccess(); // Automatic access block
54 console.log(`System ${this.id}: !!! ALARM URUCHOMIONY !!!`);
55 }
56
57 disableAlarm(): void {
58 this.czyAlarmActive = 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.czyAlarmActive) {
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.rozpocznijMonitoring();
93 this.uruchomAlarm();
94 // Access is already blocked by uruchomAlarm()
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.rozpocznijMonitoring();
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(); // Nie pozwoli unlock
114
115// Disabling alarm and unblocking
116labSecuritySystem.disableAlarm();
117labSecuritySystem.unblockAccess();What happens when we implement two interfaces with properties of the same name?
1// Two interfaces with properties of the same name
2interface SystemA {
3 id: string;
4 status: string;
5 aktywuj(): void;
6}
7
8interface SystemB {
9 id: number; // Type conflict - string vs number
10 status: string; // Same type, no conflict
11 aktywuj(kod: string): boolean; // Signature conflict
12}
13
14// Attempting to implement both interfaces
15// This implementation will cause a compilation error
16/*
17class ProblemowaSytuacja 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 aktywuj signatures
22 aktywuj(kod?: string): boolean | void {
23 if (kod) {
24 console.log(`Aktywacja z kodem: ${kod}`);
25 return true;
26 } else {
27 console.log("Aktywacja bez kodu");
28 }
29 }
30}
31*/
32
33// Correct implementation:
34// We need to create a type compatible with both interfaces
35interface SystemKombinowany 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 aktywuj(kod?: 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 callsHow to solve the problem of incompatible interfaces:
1// Better approach: Using composition instead of inheritance
2class AccessSystem {
3 private systemA: SystemAImplementacja;
4 private systemB: SystemBImplementacja;
5
6 constructor() {
7 this.systemA = new SystemAImplementacja("SYS-A");
8 this.systemB = new SystemBImplementacja(1001);
9 }
10
11 // Methods delegating to appropriate implementations
12 aktywujA(): void {
13 this.systemA.aktywuj();
14 }
15
16 aktywujB(kod: string): boolean {
17 return this.systemB.aktywuj(kod);
18 }
19
20 pobierzStatusA(): string {
21 return this.systemA.status;
22 }
23
24 pobierzStatusB(): string {
25 return this.systemB.status;
26 }
27}
28
29// Single interface implementations
30class SystemAImplementacja implements SystemA {
31 id: string;
32 status: string = "nieaktywny";
33
34 constructor(id: string) {
35 this.id = id;
36 }
37
38 aktywuj(): void {
39 this.status = "aktywny";
40 console.log(`System A (${this.id}) aktywowany`);
41 }
42}
43
44class SystemBImplementacja implements SystemB {
45 id: number;
46 status: string = "offline";
47
48 constructor(id: number) {
49 this.id = id;
50 }
51
52 aktywuj(kod: string): boolean {
53 if (kod === "secret-password") {
54 this.status = "online";
55 console.log(`System B (${this.id}) aktywowany z kodem`);
56 return true;
57 }
58 console.log(`System B (${this.id}) - incorrect activation code`);
59 return false;
60 }
61}Below is a comprehensive example using interfaces to create a management system for Jurassic Park:
1// Interface for a basic manageable entity
2interface InventoryItem {
3 readonly id: string;
4 nazwa: string;
5 category: string;
6 dataDodania: Date;
7 lastUpdate: Date;
8 status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
9
10 updateStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void;
11 pobierzInformacje(): Record<string, any>;
12}
13
14// Interface for trackable objects
15interface Tracked {
16 currentLocation: { x: number, y: number } | null;
17 historiaLokalizacji: Array<{ czas: Date, x: number, y: number }>;
18
19 updateLocation(x: number, y: number): void;
20 getLastLocation(): { czas: Date, x: number, y: number } | null;
21 getMovementRoute(fromTime: Date, toTime?: Date): Array<{ czas: Date, x: number, y: number }>;
22}
23
24// Interface for monitorable objects
25interface Monitored {
26 parameteryMonitored: Map<string, number | string | boolean>;
27 measurementHistory: Array<{ czas: Date, parameter: string, value: number | string | boolean }>;
28 monitoringStatus: "enabled" | "disabled" | "limited";
29
30 rozpocznijMonitoring(): void;
31 stopMonitoring(): void;
32 addMeasurement(parameter: string, value: number | string | boolean): void;
33 getRecentMeasurements(count?: number): Array<{ czas: Date, parameter: string, value: number | string | boolean }>;
34}
35
36// Interface for objects requiring regular maintenance
37interface Maintainable {
38 requiredLevelKonserwacji: number; // 1-5
39 lastMaintenance: Date | null;
40 planowanaKonserwacja: Date | null;
41 historiaKonserwacji: Array<{ data: Date, description: string, wykonawca: string }>;
42
43 scheduleMaintenance(data: Date): void;
44 performMaintenance(description: string, wykonawca: 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 osobyUprawnione: string[];
53
54 raiseThreatLevel(): void;
55 lowerThreatLevel(): void;
56 checkPermissions(idPracownika: 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 nazwa: string;
65 category: string = "Tracking Device";
66 dataDodania: Date;
67 lastUpdate: Date;
68 status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
69
70 // Tracked (Tracked)
71 currentLocation: { x: number, y: number } | null = null;
72 historiaLokalizacji: Array<{ czas: Date, x: number, y: number }> = [];
73
74 // Monitored (Monitored)
75 parameteryMonitored: Map<string, number | string | boolean> = new Map();
76 measurementHistory: Array<{ czas: Date, parameter: string, value: number | string | boolean }> = [];
77 monitoringStatus: "enabled" | "disabled" | "limited" = "disabled";
78
79 // Maintainable (Maintainable)
80 requiredLevelKonserwacji: number = 3;
81 lastMaintenance: Date | null = null;
82 planowanaKonserwacja: Date | null = null;
83 historiaKonserwacji: Array<{ data: Date, description: string, wykonawca: string }> = [];
84
85 // Custom properties
86 private batteryLevel: number = 100; // w procentach
87 private przypisanyDinosaur: string | null = null;
88
89 constructor(id: string, nazwa: string) {
90 this.id = id;
91 this.nazwa = nazwa;
92 this.dataDodania = new Date();
93 this.lastUpdate = new Date();
94 this.status = "nieaktywny";
95
96 // Initializing monitoring parameters
97 this.parameteryMonitored.set("bateria", this.batteryLevel);
98 this.parameteryMonitored.set("aktywny", false);
99 this.parameteryMonitored.set("temperatura", 25.0);
100 }
101
102 // Implementation of InventoryItem methods
103 updateStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void {
104 this.status = nowyStatus;
105 this.lastUpdate = new Date();
106 console.log(`Device ${this.id}: Changed status to ${nowyStatus}`);
107 }
108
109 pobierzInformacje(): Record<string, any> {
110 return {
111 id: this.id,
112 nazwa: this.nazwa,
113 category: this.category,
114 status: this.status,
115 dataDodania: this.dataDodania,
116 lastUpdate: this.lastUpdate,
117 batteryLevel: this.batteryLevel,
118 przypisanyDinosaur: this.przypisanyDinosaur
119 };
120 }
121
122 // Implementation of Tracked methods
123 updateLocation(x: number, y: number): void {
124 const czas = new Date();
125
126 // Updating current location
127 this.currentLocation = { x, y };
128
129 // Adding to history
130 this.historiaLokalizacji.push({ czas, 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(): { czas: Date, x: number, y: number } | null {
139 if (this.historiaLokalizacji.length === 0) {
140 return null;
141 }
142
143 return this.historiaLokalizacji[this.historiaLokalizacji.length - 1];
144 }
145
146 getMovementRoute(fromTime: Date, toTime: Date = new Date()): Array<{ czas: Date, x: number, y: number }> {
147 return this.historiaLokalizacji.filter(lok =>
148 lok.czas >= fromTime && lok.czas <= toTime
149 );
150 }
151
152 // Implementation of Monitored methods
153 rozpocznijMonitoring(): 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("aktywny", 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.parameteryMonitored.set("aktywny", false);
170 this.addMeasurement("aktywny", false);
171 }
172 }
173
174 addMeasurement(parameter: string, value: number | string | boolean): void {
175 const czas = new Date();
176
177 // Updating the current parameter value
178 this.parameteryMonitored.set(parameter, value);
179
180 // Adding to measurement history
181 this.measurementHistory.push({ czas, parameter, value });
182
183 // Updating the last update time
184 this.lastUpdate = czas;
185
186 console.log(`Device ${this.id}: Dodano pomiar ${parameter} = ${value}`);
187 }
188
189 getRecentMeasurements(count: number = 10): Array<{ czas: 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.planowanaKonserwacja = data;
197 console.log(`Device ${this.id}: Scheduled maintenance for ${data.toLocaleDateString()}`);
198 }
199
200 performMaintenance(description: string, wykonawca: string): void {
201 const data = new Date();
202
203 // Adding to maintenance history
204 this.historiaKonserwacji.push({ data, description, wykonawca });
205
206 // Updating the last maintenance date
207 this.lastMaintenance = data;
208
209 // Resetting planned maintenance
210 this.planowanaKonserwacja = null;
211
212 // Updating status and battery after maintenance
213 this.updateStatus("aktywny");
214 this.batteryLevel = 100;
215 this.parameteryMonitored.set("bateria", this.batteryLevel);
216 this.addMeasurement("bateria", this.batteryLevel);
217
218 console.log(`Device ${this.id}: Maintenance performed by ${wykonawca}: ${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.planowanaKonserwacja && this.planowanaKonserwacja > new Date()) {
229 return "scheduled";
230 }
231
232 // Checking if more than 90 days have passed since the last maintenance
233 const dzisiaj = new Date();
234 const differenceMilliseconds = dzisiaj.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(idDinosaura: string): void {
246 this.przypisanyDinosaur = idDinosaura;
247 this.addMeasurement("przypisanyDinosaur", idDinosaura);
248 console.log(`Device ${this.id}: Assigned to dinosaur ${idDinosaura}`);
249 }
250
251 private reduceBattery(value: number): void {
252 this.batteryLevel = Math.max(0, this.batteryLevel - value);
253 this.parameteryMonitored.set("bateria", this.batteryLevel);
254
255 // If battery is critically low - change status
256 if (this.batteryLevel < 5 && this.status === "aktywny") {
257 this.updateStatus("nieaktywny");
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 nazwa: string;
268 category: string = "Ogrodzenie Elektryczne";
269 dataDodania: Date;
270 lastUpdate: Date;
271 status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
272
273 // SecuritySystem (Security System)
274 threatLevel: 1 | 2 | 3 | 4 | 5 = 1;
275 securityZone: string;
276 osobyUprawnione: string[] = [];
277
278 // Maintainable (Maintainable)
279 requiredLevelKonserwacji: number = 5; // Highest level - critical for security
280 lastMaintenance: Date | null = null;
281 planowanaKonserwacja: Date | null = null;
282 historiaKonserwacji: Array<{ data: Date, description: string, wykonawca: string }> = [];
283
284 // Custom properties
285 private voltage: number; // w woltach
286 private fenceLength: number; // w metrach
287 private czujnikiRuchu: boolean;
288
289 constructor(
290 id: string,
291 nazwa: string,
292 securityZone: string,
293 fenceLength: number,
294 voltage: number = 10000,
295 czujnikiRuchu: boolean = true
296 ) {
297 this.id = id;
298 this.nazwa = nazwa;
299 this.securityZone = securityZone;
300 this.fenceLength = fenceLength;
301 this.voltage = voltage;
302 this.czujnikiRuchu = czujnikiRuchu;
303
304 this.dataDodania = new Date();
305 this.lastUpdate = new Date();
306 this.status = "nieaktywny";
307
308 // Adding system administrators as authorized personnel
309 this.osobyUprawnione.push("ADMIN-001", "SEC-CHIEF", "TECH-HEAD");
310 }
311
312 // Implementation of InventoryItem
313 updateStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void {
314 // If switching from inactive to active
315 if (this.status !== "aktywny" && nowyStatus === "aktywny") {
316 // Check if maintenance is required
317 if (this.checkMaintenanceStatus() === "required") {
318 console.log(`Ogrodzenie ${this.id}: WARNING - Activation without required maintenance!`);
319 }
320 }
321
322 this.status = nowyStatus;
323 this.lastUpdate = new Date();
324
325 // Automatically adjust voltage based on status
326 if (nowyStatus === "aktywny") {
327 console.log(`Ogrodzenie ${this.id}: Activated with voltage ${this.voltage}V`);
328 } else if (nowyStatus === "nieaktywny" || nowyStatus === "w naprawie") {
329 this.voltage = 0;
330 console.log(`Ogrodzenie ${this.id}: Deactivated - voltage 0V`);
331 }
332 }
333
334 pobierzInformacje(): Record<string, any> {
335 return {
336 id: this.id,
337 nazwa: this.nazwa,
338 category: this.category,
339 status: this.status,
340 securityZone: this.securityZone,
341 fenceLength: this.fenceLength,
342 voltage: this.voltage,
343 czujnikiRuchu: this.czujnikiRuchu,
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(`Ogrodzenie ${this.id}: Raised threat level to ${this.threatLevel}. New voltage: ${this.voltage}V`);
357 } else {
358 console.log(`Ogrodzenie ${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(`Ogrodzenie ${this.id}: Lowered threat level to ${this.threatLevel}. New voltage: ${this.voltage}V`);
371 } else {
372 console.log(`Ogrodzenie ${this.id}: Threat level already at minimum level 1`);
373 }
374 }
375
376 checkPermissions(idPracownika: string): boolean {
377 return this.osobyUprawnione.includes(idPracownika);
378 }
379
380 respondToThreat(description: string): void {
381 console.log(`Ogrodzenie ${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(`Ogrodzenie ${this.id}: Threat level 5. Maximum voltage: ${this.voltage}V`);
390 }
391
392 // Implementation of Maintainable
393 scheduleMaintenance(data: Date): void {
394 this.planowanaKonserwacja = data;
395 console.log(`Ogrodzenie ${this.id}: Scheduled maintenance for ${data.toLocaleDateString()}`);
396 }
397
398 performMaintenance(description: string, wykonawca: string): void {
399 const data = new Date();
400
401 // Safety measure - temporary fence deactivation during maintenance
402 const poprzedniStatus = this.status;
403 this.updateStatus("w naprawie");
404
405 // Dodanie do historii konserwacji
406 this.historiaKonserwacji.push({ data, description, wykonawca });
407
408 // Aktualizacja daty ostatniej konserwacji
409 this.lastMaintenance = data;
410
411 // Resetowanie planowanej konserwacji
412 this.planowanaKonserwacja = null;
413
414 console.log(`Ogrodzenie ${this.id}: Maintenance performed by ${wykonawca}: ${description}`);
415
416 // Restoring previous status
417 this.updateStatus(poprzedniStatus);
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.planowanaKonserwacja && this.planowanaKonserwacja > 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 dzisiaj = new Date();
433 const differenceMilliseconds = dzisiaj.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(idPracownika: string): void {
445 if (!this.osobyUprawnione.includes(idPracownika)) {
446 this.osobyUprawnione.push(idPracownika);
447 console.log(`Ogrodzenie ${this.id}: Dodano uprawnienia dla pracownika ${idPracownika}`);
448 }
449 }
450
451 removeAuthorizedPerson(idPracownika: string): void {
452 const indeks = this.osobyUprawnione.indexOf(idPracownika);
453 if (indeks !== -1) {
454 this.osobyUprawnione.splice(indeks, 1);
455 console.log(`Ogrodzenie ${this.id}: Removed permissions for employee ${idPracownika}`);
456 }
457 }
458
459 increaseVoltage(value: number): void {
460 if (this.status !== "aktywny") {
461 console.log(`Ogrodzenie ${this.id}: Cannot change voltage - fence is not active`);
462 return;
463 }
464
465 this.voltage += value;
466 console.log(`Ogrodzenie ${this.id}: Increased voltage to ${this.voltage}V`);
467 }
468
469 decreaseVoltage(value: number): void {
470 if (this.status !== "aktywny") {
471 console.log(`Ogrodzenie ${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(`Ogrodzenie ${this.id}: Decreased voltage to ${this.voltage}V`);
477 }
478}
479
480// Demonstrating the use of our implementations
481function demonstrujSystemParku() {
482 console.log("=== JURASSIC PARK MANAGEMENT SYSTEM DEMONSTRATION ===");
483
484 // Creating a tracking device
485 console.log("
486--- Initializing tracking device ---");
487 const trackingDevice = new TrackingDeviceT3000("TRACK-001", "T-Rex Tracker #1");
488 trackingDevice.updateStatus("aktywny");
489 trackingDevice.rozpocznijMonitoring();
490 trackingDevice.assignDinosaur("TREX-001");
491
492 // Simulating location tracking
493 trackingDevice.updateLocation(135.42, 86.21);
494 trackingDevice.updateLocation(135.50, 86.35);
495 trackingDevice.updateLocation(135.62, 86.40);
496
497 // Creating a fence
498 console.log("
499--- Inicjalizacja ogrodzenia ---");
500 const ogrodzenie = new ElectricFence(
501 "FENCE-002",
502 "Ogrodzenie wybiegu T-Rex",
503 "T-REX-A",
504 1200, // 1200 meters long
505 15000, // 15000V
506 true // z czujnikami ruchu
507 );
508
509 // Adding permissions for an employee
510 ogrodzenie.addAuthorizedPerson("EMP-047"); // Owen Grady
511
512 // Activating the fence
513 ogrodzenie.updateStatus("aktywny");
514
515 // Performing device maintenance
516 console.log("
517--- Device maintenance ---");
518 trackingDevice.scheduleMaintenance(new Date("2023-06-15"));
519 trackingDevice.performMaintenance("Battery replacement and GPS calibration", "Tech Ivan");
520
521 // Simulating an alarm
522 console.log("
523--- Symulacja incydentu ---");
524 console.log("T-Rex is trying to break through the fence!");
525
526 // Tracking device registers rapid movement
527 trackingDevice.updateLocation(135.90, 86.70);
528 trackingDevice.updateLocation(136.10, 86.90);
529 trackingDevice.addMeasurement("acceleration", 12.5);
530
531 // Fence responds to threat
532 ogrodzenie.respondToThreat("T-Rex escape attempt detected");
533
534 // Status checks after incident
535 console.log("
536--- Raport po incydencie ---");
537 console.log("Last dinosaur location:", trackingDevice.getLastLocation());
538 console.log("Status ogrodzenia:", ogrodzenie.pobierzInformacje());
539
540 // Returning to normall mode
541 console.log("
542--- Normalizacja sytuacji ---");
543 ogrodzenie.lowerThreatLevel();
544 ogrodzenie.lowerThreatLevel();
545
546 console.log("
547=== KONIEC DEMONSTRACJI ===");
548}
549
550// Running the demonstration
551demonstrujSystemParku();In this exercise, we learned about TypeScript interfaces - a powerful tool for defining contracts and creating consistent structures in code.
Key features of interfaces:
As Claire Dearing would summarize: "In Jurassic Park, just like in good software architecture, everyone has their role and responsibilities. Interfaces allow us to clearly define these roles, ensuring that all systems work well together - without surprises. And believe me, surprises in a park full of dinosaurs are the last thing we need."