We use cookies to enhance your experience on the site
CodeWorlds

Implementing Interfaces

"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.

Interface Basics

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:

  1. We defined the
    PracownikParku
    interface specifying what properties and methods every employee must have
  2. We created two different classes implementing this interface - each in its own way
  3. Both classes must provide all required properties and methods, but can implement them differently
  4. Classes can also add their own additional methods and properties

Optional and Read-Only Properties

TypeScript 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:

  1. We defined a
    readonly
    property that cannot be modified after initialization
  2. We added an optional property
    advancedOptions
    (marked with
    ?
    )
  3. The implementation checks whether the optional property exists before using it

Extending Interfaces

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:

  1. Animal
    - the base interface for all animals
  2. Dinosaur
    - extends
    Animal
    with properties specific to dinosaurs
  3. PredatoryDinosaur
    - extends
    Dinosaur
    with properties specific to predators

The

TyrannosaurusRex
class implements the most specialized interface
PredatoryDinosaur
, which means it must provide all properties and methods from all three interfaces.

Interfaces vs Types

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:

  1. Extensibility:

    • Interfaces can be extended later (declaration merging)
    • Types are closed after definition
    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// };
  2. Combining:

    • Interfaces use
      extends
    • Types use the intersection operator
      &
  3. Advanced constructs:

    • Types can represent unions, intersections, and other complex types
    • Interfaces are limited to describing the shape of objects
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 for Functions and Methods

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());

Index Signatures in Interfaces

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()}`);

Implementing Multiple Interfaces

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();

Implementing Interfaces with Name Conflicts

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 calls

How 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}

Practical Example: Jurassic Park Management System

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();

Summary

In this exercise, we learned about TypeScript interfaces - a powerful tool for defining contracts and creating consistent structures in code.

Key features of interfaces:

  1. Define the shape of objects - specify what properties and methods objects of a given type must have
  2. Support for polymorphism - different classes can implement the same interface in different ways
  3. Loose coupling - focus on what objects do, not how they are implemented
  4. Flexibility - optional properties, read-only, index signatures, etc.
  5. Extensibility - interfaces can inherit from other 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."

Go to CodeWorlds