Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Implementacja interfejsów

"W Parku Jurajskim wszystko musi działać według ścisłych protokołów," wyjaśnia Claire Dearing, dyrektor operacyjna. "Każdy pracownik ma jasno określony zakres obowiązków, a każdy system musi spełniać określone wymagania. Nie interesuje nas, jak dokładnie ochroniarz pilnuje wybiegu z raptorami - ważne, że wykonuje swoją pracę zgodnie z procedurami bezpieczeństwa."

W świecie TypeScript, interfejsy pełnią podobną rolę - definiują "kontrakt", który klasy muszą spełnić, bez określania, jak dokładnie mają to zrobić. W tym ćwiczeniu dowiesz się, jak projektować i implementować interfejsy, tworząc zaawansowany system zarządzania Parkiem Jurajskim.

Podstawy interfejsów

Interfejs w TypeScript to struktura, która definiuje kształt obiektu - jakie właściwości i metody powinien posiadać, bez określania ich implementacji. Możesz myśleć o interfejsie jak o kontrakcie lub protokole, który klasa musi przestrzegać.

Przyjrzyjmy się podstawowej składni interfejsu:

1// Definicja interfejsu
2interface PracownikParku {
3  // Właściwości
4  id: string;
5  imię: string;
6  nazwisko: string;
7  poziomDostępu: number;
8
9  // Metody
10  zaloguj(): void;
11  wyloguj(): void;
12  sprawdźUprawnienia(wymaganyPoziom: number): boolean;
13}
14
15// Klasa implementująca interfejs
16class OchroniarzParku implements PracownikParku {
17  // Implementacja wszystkich wymaganych właściwości
18  id: string;
19  imię: string;
20  nazwisko: string;
21  poziomDostępu: number;
22  private aktywnyStatus: boolean = false;
23
24  constructor(id: string, imię: string, nazwisko: string) {
25    this.id = id;
26    this.imię = imię;
27    this.nazwisko = nazwisko;
28    this.poziomDostępu = 2; // Standardowy poziom dla ochroniarzy
29  }
30
31  // Implementacja wszystkich wymaganych metod
32  zaloguj(): void {
33    console.log(`Ochroniarz ${this.imię} ${this.nazwisko} zalogował się do systemu.`);
34    this.aktywnyStatus = true;
35  }
36
37  wyloguj(): void {
38    console.log(`Ochroniarz ${this.imię} ${this.nazwisko} wylogował się z systemu.`);
39    this.aktywnyStatus = false;
40  }
41
42  sprawdźUprawnienia(wymaganyPoziom: number): boolean {
43    return this.poziomDostępu >= wymaganyPoziom;
44  }
45
46  // Dodatkowe metody specyficzne dla ochroniarza
47  patrolujSektor(sektorId: string): void {
48    if (!this.aktywnyStatus) {
49      console.log("Błąd: Ochroniarz musi być zalogowany, aby patrolować sektor.");
50      return;
51    }
52
53    console.log(`Ochroniarz ${this.imię} ${this.nazwisko} patroluje sektor ${sektorId}.`);
54  }
55}
56
57// Inna klasa implementująca ten sam interfejs
58class NaukowiecParku implements PracownikParku {
59  id: string;
60  imię: string;
61  nazwisko: string;
62  poziomDostępu: number;
63  private specjalizacja: string;
64
65  constructor(id: string, imię: string, nazwisko: string, specjalizacja: string) {
66    this.id = id;
67    this.imię = imię;
68    this.nazwisko = nazwisko;
69    this.poziomDostępu = 4; // Wyższy poziom dostępu dla naukowców
70    this.specjalizacja = specjalizacja;
71  }
72
73  zaloguj(): void {
74    console.log(`Dr ${this.imię} ${this.nazwisko} zalogował się do systemu laboratorium.`);
75  }
76
77  wyloguj(): void {
78    console.log(`Dr ${this.imię} ${this.nazwisko} wylogował się z systemu laboratorium.`);
79  }
80
81  sprawdźUprawnienia(wymaganyPoziom: number): boolean {
82    // Naukowcy mają specjalne uprawnienia w laboratoriach
83    if (wymaganyPoziom === 3 && this.specjalizacja === "genetyka") {
84      return true;
85    }
86    return this.poziomDostępu >= wymaganyPoziom;
87  }
88
89  // Metoda specyficzna dla naukowca
90  przeprowadźBadanie(obiektBadań: string): void {
91    console.log(`Dr ${this.imię} ${this.nazwisko} prowadzi badania nad: ${obiektBadań}.`);
92  }
93}
94
95// Użycie klas implementujących interfejs
96const ochroniarz = new OchroniarzParku("SEC-001", "John", "Smith");
97const naukowiec = new NaukowiecParku("SCI-042", "Henry", "Wu", "genetyka");
98
99// Obie klasy implementują ten sam interfejs, więc możemy użyć tych samych metod
100ochroniarz.zaloguj();
101naukowiec.zaloguj();
102
103// Sprawdzenie uprawnień
104console.log(`Ochroniarz ma dostęp do strefy 2: ${ochroniarz.sprawdźUprawnienia(2)}`);
105console.log(`Naukowiec ma dostęp do strefy 3: ${naukowiec.sprawdźUprawnienia(3)}`);
106
107// Wywołanie metod specyficznych dla konkretnych klas
108ochroniarz.patrolujSektor("B-5");
109naukowiec.przeprowadźBadanie("DNA Velociraptora");

W powyższym przykładzie:

  1. Zdefiniowaliśmy interfejs
    PracownikParku
    określający, jakie właściwości i metody musi mieć każdy pracownik
  2. Utworzyliśmy dwie różne klasy implementujące ten interfejs - każda na swój sposób
  3. Obie klasy muszą dostarczyć wszystkie wymagane właściwości i metody, ale mogą implementować je różnie
  4. Klasy mogą również dodawać własne, dodatkowe metody i właściwości

Właściwości opcjonalne i tylko do odczytu

Interfejsy TypeScript pozwalają na bardziej zaawansowane definiowanie właściwości:

1interface SystemBezpieczeństwa {
2  // Wymagana właściwość
3  id: string;
4
5  // Właściwość tylko do odczytu - nie można jej modyfikować po inicjalizacji
6  readonly typSystemu: "monitoring" | "ogrodzenie" | "czujnikRuchu" | "alarm";
7
8  // Właściwość opcjonalna - może istnieć lub nie
9  opcjeZaawansowane?: Record<string, any>;
10
11  // Metody
12  aktywuj(): void;
13  deaktywuj(): void;
14  sprawdźStatus(): string;
15}
16
17class SystemOgrodzenia implements SystemBezpieczeństwa {
18  id: string;
19  readonly typSystemu: "monitoring" | "ogrodzenie" | "czujnikRuchu" | "alarm" = "ogrodzenie";
20  opcjeZaawansowane?: Record<string, any>; // Opcjonalna właściwość
21
22  private napięcie: number = 0;
23  private aktywny: boolean = false;
24
25  constructor(id: string, opcjeZaawansowane?: Record<string, any>) {
26    this.id = id;
27    if (opcjeZaawansowane) {
28      this.opcjeZaawansowane = opcjeZaawansowane;
29    }
30  }
31
32  aktywuj(): void {
33    this.aktywny = true;
34    this.napięcie = this.opcjeZaawansowane?.napięcieStartowe || 10000; // Użycie opcjonalnej właściwości
35    console.log(`System ogrodzenia ${this.id} aktywowany. Napięcie: ${this.napięcie}V`);
36  }
37
38  deaktywuj(): void {
39    this.aktywny = false;
40    this.napięcie = 0;
41    console.log(`System ogrodzenia ${this.id} deaktywowany.`);
42  }
43
44  sprawdźStatus(): string {
45    return `System ogrodzenia ${this.id}: ${this.aktywny ? "AKTYWNY" : "NIEAKTYWNY"}, Napięcie: ${this.napięcie}V`;
46  }
47
48  // Własna metoda, niewymagana przez interfejs
49  zwiększNapięcie(przyrost: number): void {
50    if (!this.aktywny) {
51      console.log("Nie można zmienić napięcia - system nieaktywny.");
52      return;
53    }
54
55    this.napięcie += przyrost;
56    console.log(`Zwiększono napięcie do ${this.napięcie}V.`);
57  }
58}
59
60// Użycie z opcjonalnymi właściwościami
61const ogrodzenieTRex = new SystemOgrodzenia("FNC-TR1", {
62  napięcieStartowe: 20000,
63  automatyczneRestarty: true
64});
65
66const ogrodzenieStandardowe = new SystemOgrodzenia("FNC-ST1");
67
68ogrodzenieTRex.aktywuj();   // Używa niestandardowego napięcia startowego
69ogrodzenieStandardowe.aktywuj();  // Używa domyślnego napięcia startowego
70
71// Error: Nie można przypisać do 'typSystemu' ponieważ jest to właściwość tylko do odczytu
72// ogrodzenieTRex.typSystemu = "alarm";

W tym przykładzie:

  1. Zdefiniowaliśmy właściwość
    readonly
    , której nie można modyfikować po inicjalizacji
  2. Dodaliśmy opcjonalną właściwość
    opcjeZaawansowane
    (oznaczoną przez
    ?
    )
  3. Implementacja sprawdza, czy opcjonalna właściwość istnieje, zanim jej użyje

Rozszerzanie interfejsów

Interfejsy, podobnie jak klasy, mogą rozszerzać inne interfejsy, dziedzicząc ich właściwości i metody:

1// Podstawowy interfejs dla wszystkich zwierząt
2interface Zwierzę {
3  id: string;
4  gatunek: string;
5  waga: number;
6  karma(): void;
7  monitoruj(): string;
8}
9
10// Rozszerzony interfejs specyficzny dla dinozaurów
11interface Dinozaur extends Zwierzę {
12  poziomNiebezpieczeństwa: number;
13  eraDinozaura: "trias" | "jura" | "kreda";
14  podajSpecjalnąDietę(): string;
15}
16
17// Jeszcze bardziej wyspecjalizowany interfejs
18interface DrapieżnyDinozaur extends Dinozaur {
19  siłaUgryzienia: number;
20  prędkość: number;
21  poluj(): void;
22}
23
24// Implementacja
25class TyranozaurRex implements DrapieżnyDinozaur {
26  // Właściwości z interfejsu Zwierzę
27  id: string;
28  gatunek: string = "Tyrannosaurus Rex";
29  waga: number;
30
31  // Właściwości z interfejsu Dinozaur
32  poziomNiebezpieczeństwa: number = 10;
33  eraDinozaura: "trias" | "jura" | "kreda" = "kreda";
34
35  // Właściwości z interfejsu DrapieżnyDinozaur
36  siłaUgryzienia: number;
37  prędkość: number;
38
39  // Własne właściwości
40  private wiek: number;
41  private ostatnieKarmienie: Date | null = null;
42
43  constructor(id: string, waga: number, wiek: number, siłaUgryzienia: number, prędkość: number) {
44    this.id = id;
45    this.waga = waga;
46    this.wiek = wiek;
47    this.siłaUgryzienia = siłaUgryzienia;
48    this.prędkość = prędkość;
49  }
50
51  // Implementacja metod z interfejsu Zwierzę
52  karma(): void {
53    this.ostatnieKarmienie = new Date();
54    console.log(`T-Rex ${this.id} został nakarmiony.`);
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}: Waga: ${this.waga}kg, Wiek: ${this.wiek} lat. ${statusKarmienia}`;
63  }
64
65  // Implementacja metody z interfejsu Dinozaur
66  podajSpecjalnąDietę(): string {
67    return "Świeże mięso ssaków, preferowana wołowina, 250kg dziennie.";
68  }
69
70  // Implementacja metody z interfejsu DrapieżnyDinozaur
71  poluj(): void {
72    console.log(`T-Rex ${this.id} poluje z prędkością ${this.prędkość} km/h i siłą ugryzienia ${this.siłaUgryzienia} PSI!`);
73  }
74}
75
76// Użycie
77const rex = new TyranozaurRex("TREX-01", 8000, 12, 12800, 30);
78rex.karma();
79console.log(rex.monitoruj());
80console.log(`Specjalna dieta: ${rex.podajSpecjalnąDietę()}`);
81rex.poluj();

W tym przykładzie stworzyliśmy hierarchię interfejsów:

  1. Zwierzę
    - podstawowy interfejs dla wszystkich zwierząt
  2. Dinozaur
    - rozszerza
    Zwierzę
    o właściwości specyficzne dla dinozaurów
  3. DrapieżnyDinozaur
    - rozszerza
    Dinozaur
    o właściwości specyficzne dla drapieżników

Klasa

TyranozaurRex
implementuje najbardziej wyspecjalizowany interfejs
DrapieżnyDinozaur
, co oznacza, że musi dostarczyć wszystkie właściwości i metody ze wszystkich trzech interfejsów.

Interfejsy vs Typy

TypeScript oferuje również aliasy typów (

type
), które mają podobne możliwości co interfejsy, ale istnieją między nimi pewne różnice:

1// Interfejs
2interface PomiarWybieg {
3  szerokość: number;
4  długość: number;
5  obliczPowierzchnię(): number;
6}
7
8// Typ (Type alias)
9type PomiarKlatka = {
10  szerokość: number;
11  wysokość: number;
12  głębokość: number;
13  obliczObjętość(): number;
14};
15
16// Rozszerzanie interfejsu
17interface PomiarWybiegZOgrodzeniem extends PomiarWybieg {
18  wysokośćOgrodzenia: number;
19  materiałOgrodzenia: string;
20}
21
22// "Rozszerzanie" typu poprzez przecięcie (intersection)
23type PomiarKlatkaWzmocniona = PomiarKlatka & {
24  materiałWzmocnienia: string;
25  maksymalneObciążenie: number;
26};
27
28// Implementacja interfejsu
29class WybiegRaptorów implements PomiarWybiegZOgrodzeniem {
30  szerokość: number;
31  długość: number;
32  wysokośćOgrodzenia: number;
33  materiałOgrodzenia: string;
34
35  constructor(szerokość: number, długość: number, wysokośćOgrodzenia: number) {
36    this.szerokość = szerokość;
37    this.długość = długość;
38    this.wysokośćOgrodzenia = wysokośćOgrodzenia;
39    this.materiałOgrodzenia = "Stal wzmocniona";
40  }
41
42  obliczPowierzchnię(): number {
43    return this.szerokość * this.długość;
44  }
45}
46
47// Implementacja typu (działa tak samo jak interfejs)
48class KlatkaTransportowa implements PomiarKlatkaWzmocniona {
49  szerokość: number;
50  wysokość: number;
51  głębokość: number;
52  materiałWzmocnienia: string;
53  maksymalneObciążenie: number;
54
55  constructor(
56    szerokość: number,
57    wysokość: number,
58    głębokość: number,
59    maksymalneObciążenie: number
60  ) {
61    this.szerokość = szerokość;
62    this.wysokość = wysokość;
63    this.głębokość = głębokość;
64    this.materiałWzmocnienia = "Kompozyt węglowy";
65    this.maksymalneObciążenie = maksymalneObciążenie;
66  }
67
68  obliczObjętość(): number {
69    return this.szerokość * this.wysokość * this.głębokość;
70  }
71}

Główne różnice między interfejsami a typami:

  1. Rozszerzalność:

    • Interfejsy można rozszerzyć później (deklaracja fuzji)
    • Typy są zamknięte po zdefiniowaniu
    1// Interfejs można rozszerzyć dodatkowymi deklaracjami
    2interface DiagnozaDinozaura {
    3  wynikBadania: string;
    4}
    5
    6// Dodawanie nowych właściwości do istniejącego interfejsu
    7interface DiagnozaDinozaura {
    8  dataWykonania: Date;
    9}
    10
    11// Teraz DiagnozaDinozaura ma obie właściwości!
    12
    13// Dla typów to nie zadziała:
    14type OpisWybieg = {
    15  id: string;
    16};
    17
    18// Error: Duplicate identifier 'OpisWybieg'
    19// type OpisWybieg = {
    20//   nazwa: string;
    21// };
  2. Łączenie:

    • Interfejsy używają
      extends
    • Typy używają operatora przecięcia
      &
  3. Zaawansowane konstrukcje:

    • Typy mogą reprezentować unie, przecięcia i inne złożone typy
    • Interfejsy są ograniczone do opisywania kształtu obiektów
1// Zaawansowane typy, których nie można wyrazić interfejsami
2type StatusDinozaura = "zdrowy" | "chory" | "w trakcie leczenia" | "poddany kwarantannie";
3
4type IDDinozaura = string | number;
5
6type ZwierzęLubRoślina = Zwierzę | { rodzaj: string, wysokość: number };
7
8// Mapowane typy
9type Opcjonalny<T> = {
10  [P in keyof T]?: T[P];
11};
12
13// Użycie mapowanego typu z interfejsem
14type OpcjonalnyDinozaur = Opcjonalny<Dinozaur>;

Interfejsy dla funkcji i metod

Interfejsy mogą również definiować kształt funkcji i konstrukcji klas:

1// Interfejs dla funkcji
2interface ProcesowanieZdjęcia {
3  (ścieżka: string, opcje?: { rozdzielczość?: number, filtr?: string }): Promise<string>;
4}
5
6// Implementacja funkcji zgodnej z interfejsem
7const procesorZdjęćDinozaurów: ProcesowanieZdjęcia = async (ścieżka, opcje) => {
8  console.log(`Przetwarzanie zdjęcia: ${ścieżka}`);
9  console.log(`Opcje: ${JSON.stringify(opcje || {})}`);
10
11  // Symulacja przetwarzania
12  await new Promise(resolve => setTimeout(resolve, 1000));
13
14  return `zoptymalizowane_${ścieżka}`;
15};
16
17// Wywołanie funkcji
18procesorZdjęćDinozaurów("trex.jpg", { rozdzielczość: 1200 })
19  .then(wynik => console.log(`Przetworzono: ${wynik}`));
20
21// Interfejs dla konstruktora klasy
22interface KonstruktorDinozaura {
23  new (id: string, gatunek: string, wiek: number): Zwierzę;
24}
25
26// Funkcja używająca konstruktora
27function utworzDinozaura(Konstruktor: KonstruktorDinozaura, id: string, gatunek: string, wiek: number): Zwierzę {
28  return new Konstruktor(id, gatunek, wiek);
29}
30
31// Klasa zgodna z interfejsem konstruktora
32class PrototypDinozaura implements Zwierzę {
33  id: string;
34  gatunek: string;
35  waga: 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._wiek = wiek;
42
43    // Kalkulacja wagi na podstawie wieku
44    this.waga = wiek * 100; // Uproszczona kalkulacja
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// Użycie funkcji fabrycznej
57const nowyDinozaur = utworzDinozaura(PrototypDinozaura, "PROTO-1", "Testosaurus", 5);
58console.log(nowyDinozaur.monitoruj());

Indeksy w interfejsach

Interfejsy mogą również definiować typy indeksowane, które pozwalają na dynamiczny dostęp do właściwości:

1// Interfejs z indeksami
2interface BazaDanychZwierząt {
3  // Indeks typu string, wartości typu Zwierzę
4  [id: string]: Zwierzę;
5
6  // Możemy również dodać konkretne właściwości
7  liczbaZwierząt: number;
8  ostatniaAktualizacja: Date;
9}
10
11// Implementacja
12class RepositoryZwierząt implements BazaDanychZwierząt {
13  // Implementacja indeksu
14  [id: string]: Zwierzę | number | Date | Function;
15
16  liczbaZwierząt: number = 0;
17  ostatniaAktualizacja: Date = new Date();
18
19  // Musimy obsłużyć metody, ponieważ również są właściwościami
20  dodajZwierzę(zwierzę: Zwierzę): void {
21    this[zwierzę.id] = zwierzę;
22    this.liczbaZwierząt++;
23    this.ostatniaAktualizacja = new Date();
24  }
25
26  pobierzZwierzę(id: string): Zwierzę | undefined {
27    const zwierzę = this[id];
28    if (zwierzę && typeof zwierzę !== 'number' && !(zwierzę instanceof Date) && typeof zwierzę !== 'function') {
29      return zwierzę;
30    }
31    return undefined;
32  }
33}
34
35// Użycie
36const repoZwierząt = new RepositoryZwierząt();
37
38// Dodanie kilku zwierząt
39const triceratops = {
40  id: "TRIC-01",
41  gatunek: "Triceratops",
42  waga: 6000,
43  karma(): void { console.log("Karmienie Triceratopsa"); },
44  monitoruj(): string { return "Monitoring Triceratopsa"; }
45};
46
47const stegozaur = {
48  id: "STEG-01",
49  gatunek: "Stegosaurus",
50  waga: 5000,
51  karma(): void { console.log("Karmienie Stegozaura"); },
52  monitoruj(): string { return "Monitoring Stegozaura"; }
53};
54
55repoZwierząt.dodajZwierzę(triceratops);
56repoZwierząt.dodajZwierzę(stegozaur);
57
58// Dostęp przez indeksy
59const zwierzę = repoZwierząt.pobierzZwierzę("TRIC-01");
60if (zwierzę) {
61  console.log(`Znaleziono: ${zwierzę.gatunek}`);
62  zwierzę.karma();
63}
64
65console.log(`Liczba zwierząt w repozytorium: ${repoZwierząt.liczbaZwierząt}`);
66console.log(`Ostatnia aktualizacja: ${repoZwierząt.ostatniaAktualizacja.toLocaleString()}`);

Implementacja wielu interfejsów

Klasa może implementować wiele interfejsów jednocześnie:

1// Interfejs dla możliwości monitorowania
2interface Monitorowalny {
3  rozpocznijMonitoring(): void;
4  zakończMonitoring(): void;
5  pobierzStatusMonitoringu(): string;
6}
7
8// Interfejs dla systemów alarmowych
9interface SystemAlarmowy {
10  uruchomAlarm(): void;
11  wyłączAlarm(): void;
12  czyAlarmAktywny: boolean;
13}
14
15// Interfejs dla systemów kontroli dostępu
16interface KontrolaDostępu {
17  sprawdźDostęp(poziomUprawnień: number): boolean;
18  zablokujDostęp(): void;
19  odblokujDostęp(): void;
20}
21
22// Klasa implementująca wszystkie trzy interfejsy
23class ZaawansowanySystemBezpieczeństwa implements Monitorowalny, SystemAlarmowy, KontrolaDostępu {
24  private id: string;
25  private aktywnyMonitoring: boolean = false;
26  czyAlarmAktywny: boolean = false;
27  private dostępZablokowany: boolean = false;
28  private minimalnyPoziomUprawnień: number;
29
30  constructor(id: string, minimalnyPoziomUprawnień: number = 3) {
31    this.id = id;
32    this.minimalnyPoziomUprawnień = minimalnyPoziomUprawnień;
33  }
34
35  // Implementacja Monitorowalny
36  rozpocznijMonitoring(): void {
37    this.aktywnyMonitoring = true;
38    console.log(`System ${this.id}: Rozpoczęto monitoring.`);
39  }
40
41  zakończMonitoring(): void {
42    this.aktywnyMonitoring = false;
43    console.log(`System ${this.id}: Zakończono monitoring.`);
44  }
45
46  pobierzStatusMonitoringu(): string {
47    return `System ${this.id}: Monitoring ${this.aktywnyMonitoring ? "aktywny" : "nieaktywny"}`;
48  }
49
50  // Implementacja SystemAlarmowy
51  uruchomAlarm(): void {
52    this.czyAlarmAktywny = true;
53    this.zablokujDostęp(); // Automatyczna blokada dostępu
54    console.log(`System ${this.id}: !!! ALARM URUCHOMIONY !!!`);
55  }
56
57  wyłączAlarm(): void {
58    this.czyAlarmAktywny = false;
59    console.log(`System ${this.id}: Alarm wyłączony.`);
60  }
61
62  // Implementacja KontrolaDostępu
63  sprawdźDostęp(poziomUprawnień: number): boolean {
64    if (this.dostępZablokowany) {
65      console.log(`System ${this.id}: Dostęp zablokowany dla wszystkich poziomów uprawnień.`);
66      return false;
67    }
68
69    const dostępPrzyznany = poziomUprawnień >= this.minimalnyPoziomUprawnień;
70    console.log(`System ${this.id}: Sprawdzanie dostępu dla poziomu ${poziomUprawnień}: ${dostępPrzyznany ? "PRZYZNANY" : "ODMOWA"}`);
71    return dostępPrzyznany;
72  }
73
74  zablokujDostęp(): void {
75    this.dostępZablokowany = true;
76    console.log(`System ${this.id}: Dostęp zablokowany.`);
77  }
78
79  odblokujDostęp(): void {
80    if (this.czyAlarmAktywny) {
81      console.log(`System ${this.id}: Nie można odblokować dostępu - alarm aktywny!`);
82      return;
83    }
84
85    this.dostępZablokowany = false;
86    console.log(`System ${this.id}: Dostęp odblokowany.`);
87  }
88
89  // Własna metoda łącząca funkcjonalności z różnych interfejsów
90  reagujNaWłamanie(): void {
91    console.log(`System ${this.id}: Wykryto próbę włamania!`);
92    this.rozpocznijMonitoring();
93    this.uruchomAlarm();
94    // Dostęp jest już zablokowany przez uruchomAlarm()
95  }
96}
97
98// Użycie klasy implementującej wiele interfejsów
99const systemZabezpieczeńLab = new ZaawansowanySystemBezpieczeństwa("SEC-LAB-01", 4);
100
101// Możemy użyć metod z różnych interfejsów
102systemZabezpieczeńLab.rozpocznijMonitoring();
103console.log(systemZabezpieczeńLab.pobierzStatusMonitoringu());
104
105// Sprawdzenie dostępu
106console.log(systemZabezpieczeńLab.sprawdźDostęp(3)); // Odmowa - wymagany poziom 4
107console.log(systemZabezpieczeńLab.sprawdźDostęp(5)); // Dostęp przyznany
108
109// Reakcja na włamanie
110systemZabezpieczeńLab.reagujNaWłamanie();
111
112// Próba odblokowania dostępu podczas alarmu
113systemZabezpieczeńLab.odblokujDostęp(); // Nie pozwoli odblokować
114
115// Wyłączenie alarmu i odblokowanie
116systemZabezpieczeńLab.wyłączAlarm();
117systemZabezpieczeńLab.odblokujDostęp();

Implementacja interfejsów z konfliktami nazw

Co się stanie, gdy implementujemy dwa interfejsy z właściwościami o tych samych nazwach?

1// Dwa interfejsy z właściwościami o tej samej nazwie
2interface SystemA {
3  id: string;
4  status: string;
5  aktywuj(): void;
6}
7
8interface SystemB {
9  id: number; // Konflikt typu - string vs number
10  status: string; // Taki sam typ, brak konfliktu
11  aktywuj(kod: string): boolean; // Konflikt sygnatury
12}
13
14// Próba implementacji obu interfejsów
15// Ta implementacja spowoduje błąd kompilacji
16/*
17class ProblemowaSytuacja implements SystemA, SystemB {
18  id: string; // Nie może być jednocześnie string i number
19  status: string; // OK, ten sam typ w obu interfejsach
20
21  // Próba implementacji obu sygnatur aktywuj
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// Poprawna implementacja:
34// Musimy stworzyć typ, który jest kompatybilny z oboma interfejsami
35interface SystemKombinowany extends SystemA, SystemB {
36  // Rozwiązanie konfliktu id
37  id: string & number; // Musi być zarówno string jak i number (niemożliwe w praktyce)
38
39  // Status bez konfliktu
40  status: string;
41
42  // Rozwiązanie konfliktu metody
43  aktywuj(kod?: string): boolean | void;
44}
45
46// W praktyce, często niemożliwe jest zaimplementowanie obu interfejsów jednocześnie
47// W takim przypadku lepiej zmodyfikować jeden z interfejsów lub
48// stworzyć adapter, który przekierowuje wywołania

Jak rozwiązać problem niekompatybilnych interfejsów:

1// Lepsze podejście: Używanie kompozycji zamiast dziedziczenia
2class SystemDostępu {
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  // Metody delegujące do odpowiednich implementacji
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// Implementacje pojedynczych interfejsów
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 === "tajne-hasło") {
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}) - błędny kod aktywacji`);
59    return false;
60  }
61}

Praktyczny przykład: System zarządzania Parkiem Jurajskim

Poniżej znajduje się kompleksowy przykład wykorzystujący interfejsy do stworzenia systemu zarządzania dla Parku Jurajskiego:

1// Interfejs dla podstawowego bytu zarządzalnego
2interface PozycjaInwentarza {
3  readonly id: string;
4  nazwa: string;
5  kategoria: string;
6  dataDodania: Date;
7  ostatniaAktualizacja: Date;
8  status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
9
10  aktualizujStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void;
11  pobierzInformacje(): Record<string, any>;
12}
13
14// Interfejs dla obiektów, które mogą być śledzone
15interface Śledzone {
16  lokalizacjaBieżąca: { x: number, y: number } | null;
17  historiaLokalizacji: Array<{ czas: Date, x: number, y: number }>;
18
19  aktualizujLokalizację(x: number, y: number): void;
20  pobierzOstatniaLokalizację(): { czas: Date, x: number, y: number } | null;
21  pobierzTraséRuchu(odCzas: Date, doCzas?: Date): Array<{ czas: Date, x: number, y: number }>;
22}
23
24// Interfejs dla obiektów, które mogą być monitorowane
25interface Monitorowane {
26  parametryMonitorowane: Map<string, number | string | boolean>;
27  historiaPomiarów: Array<{ czas: Date, parametr: string, wartość: number | string | boolean }>;
28  statusMonitoringu: "włączony" | "wyłączony" | "ograniczony";
29
30  rozpocznijMonitoring(): void;
31  zakończMonitoring(): void;
32  dodajPomiar(parametr: string, wartość: number | string | boolean): void;
33  pobierzOstatniePomiary(liczba?: number): Array<{ czas: Date, parametr: string, wartość: number | string | boolean }>;
34}
35
36// Interfejs dla obiektów wymagających regularnej konserwacji
37interface Konserwowalny {
38  wymaganyPoziomKonserwacji: number; // 1-5
39  ostatniaKonserwacja: Date | null;
40  planowanaKonserwacja: Date | null;
41  historiaKonserwacji: Array<{ data: Date, opis: string, wykonawca: string }>;
42
43  zaplanujKonserwację(data: Date): void;
44  wykonajKonserwację(opis: string, wykonawca: string): void;
45  sprawdźStatusKonserwacji(): "wymagana" | "zaplanowana" | "aktualna";
46}
47
48// Interfejs dla systemów bezpieczeństwa
49interface SystemBezpieczeństwa {
50  poziomZagrożenia: 1 | 2 | 3 | 4 | 5;
51  strefaBezpieczeństwa: string;
52  osobyUprawnione: string[];
53
54  podnieśPoziomZagrożenia(): void;
55  obniżPoziomZagrożenia(): void;
56  sprawdźUprawnienia(idPracownika: string): boolean;
57  reagujNaZagrożenie(opis: string): void;
58}
59
60// Implementacja urządzenia monitorującego dinozaury
61class UrządzenieŚledząceT3000 implements PozycjaInwentarza, Śledzone, Monitorowane, Konserwowalny {
62  // PozycjaInwentarza
63  readonly id: string;
64  nazwa: string;
65  kategoria: string = "Urządzenie Śledzące";
66  dataDodania: Date;
67  ostatniaAktualizacja: Date;
68  status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
69
70  // Śledzone
71  lokalizacjaBieżąca: { x: number, y: number } | null = null;
72  historiaLokalizacji: Array<{ czas: Date, x: number, y: number }> = [];
73
74  // Monitorowane
75  parametryMonitorowane: Map<string, number | string | boolean> = new Map();
76  historiaPomiarów: Array<{ czas: Date, parametr: string, wartość: number | string | boolean }> = [];
77  statusMonitoringu: "włączony" | "wyłączony" | "ograniczony" = "wyłączony";
78
79  // Konserwowalny
80  wymaganyPoziomKonserwacji: number = 3;
81  ostatniaKonserwacja: Date | null = null;
82  planowanaKonserwacja: Date | null = null;
83  historiaKonserwacji: Array<{ data: Date, opis: string, wykonawca: string }> = [];
84
85  // Własne właściwości
86  private bateriaPoziom: number = 100; // w procentach
87  private przypisanyDinozaur: 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.ostatniaAktualizacja = new Date();
94    this.status = "nieaktywny";
95
96    // Inicjalizacja parametrów monitorowania
97    this.parametryMonitorowane.set("bateria", this.bateriaPoziom);
98    this.parametryMonitorowane.set("aktywny", false);
99    this.parametryMonitorowane.set("temperatura", 25.0);
100  }
101
102  // Implementacja metod PozycjaInwentarza
103  aktualizujStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void {
104    this.status = nowyStatus;
105    this.ostatniaAktualizacja = new Date();
106    console.log(`Urządzenie ${this.id}: Zmieniono status na ${nowyStatus}`);
107  }
108
109  pobierzInformacje(): Record<string, any> {
110    return {
111      id: this.id,
112      nazwa: this.nazwa,
113      kategoria: this.kategoria,
114      status: this.status,
115      dataDodania: this.dataDodania,
116      ostatniaAktualizacja: this.ostatniaAktualizacja,
117      bateriaPoziom: this.bateriaPoziom,
118      przypisanyDinozaur: this.przypisanyDinozaur
119    };
120  }
121
122  // Implementacja metod Śledzone
123  aktualizujLokalizację(x: number, y: number): void {
124    const czas = new Date();
125
126    // Aktualizacja bieżącej lokalizacji
127    this.lokalizacjaBieżąca = { x, y };
128
129    // Dodanie do historii
130    this.historiaLokalizacji.push({ czas, x, y });
131
132    // Symulacja zużycia baterii
133    this.zmniejszBaterię(0.1);
134
135    console.log(`Urządzenie ${this.id}: Zaktualizowano lokalizację na [x: ${x}, y: ${y}]`);
136  }
137
138  pobierzOstatniaLokalizację(): { 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  pobierzTraséRuchu(odCzas: Date, doCzas: Date = new Date()): Array<{ czas: Date, x: number, y: number }> {
147    return this.historiaLokalizacji.filter(lok =>
148      lok.czas >= odCzas && lok.czas <= doCzas
149    );
150  }
151
152  // Implementacja metod Monitorowane
153  rozpocznijMonitoring(): void {
154    if (this.statusMonitoringu !== "włączony") {
155      this.statusMonitoringu = "włączony";
156      console.log(`Urządzenie ${this.id}: Rozpoczęto monitoring`);
157
158      // Symulacja pomiaru przy włączeniu monitoringu
159      this.dodajPomiar("aktywny", true);
160    }
161  }
162
163  zakończMonitoring(): void {
164    if (this.statusMonitoringu !== "wyłączony") {
165      this.statusMonitoringu = "wyłączony";
166      console.log(`Urządzenie ${this.id}: Zakończono monitoring`);
167
168      // Aktualizacja parametru aktywności
169      this.parametryMonitorowane.set("aktywny", false);
170      this.dodajPomiar("aktywny", false);
171    }
172  }
173
174  dodajPomiar(parametr: string, wartość: number | string | boolean): void {
175    const czas = new Date();
176
177    // Aktualizacja bieżącej wartości parametru
178    this.parametryMonitorowane.set(parametr, wartość);
179
180    // Dodanie do historii pomiarów
181    this.historiaPomiarów.push({ czas, parametr, wartość });
182
183    // Aktualizacja czasu ostatniej aktualizacji
184    this.ostatniaAktualizacja = czas;
185
186    console.log(`Urządzenie ${this.id}: Dodano pomiar ${parametr} = ${wartość}`);
187  }
188
189  pobierzOstatniePomiary(liczba: number = 10): Array<{ czas: Date, parametr: string, wartość: number | string | boolean }> {
190    // Zwraca określoną liczbę ostatnich pomiarów
191    return this.historiaPomiarów.slice(-liczba);
192  }
193
194  // Implementacja metod Konserwowalny
195  zaplanujKonserwację(data: Date): void {
196    this.planowanaKonserwacja = data;
197    console.log(`Urządzenie ${this.id}: Zaplanowano konserwację na ${data.toLocaleDateString()}`);
198  }
199
200  wykonajKonserwację(opis: string, wykonawca: string): void {
201    const data = new Date();
202
203    // Dodanie do historii konserwacji
204    this.historiaKonserwacji.push({ data, opis, wykonawca });
205
206    // Aktualizacja daty ostatniej konserwacji
207    this.ostatniaKonserwacja = data;
208
209    // Resetowanie planowanej konserwacji
210    this.planowanaKonserwacja = null;
211
212    // Aktualizacja statusu i baterii po konserwacji
213    this.aktualizujStatus("aktywny");
214    this.bateriaPoziom = 100;
215    this.parametryMonitorowane.set("bateria", this.bateriaPoziom);
216    this.dodajPomiar("bateria", this.bateriaPoziom);
217
218    console.log(`Urządzenie ${this.id}: Wykonano konserwację przez ${wykonawca}: ${opis}`);
219  }
220
221  sprawdźStatusKonserwacji(): "wymagana" | "zaplanowana" | "aktualna" {
222    // Brak wcześniejszej konserwacji - wymagana
223    if (!this.ostatniaKonserwacja) {
224      return "wymagana";
225    }
226
227    // Zaplanowana konserwacja w przyszłości
228    if (this.planowanaKonserwacja && this.planowanaKonserwacja > new Date()) {
229      return "zaplanowana";
230    }
231
232    // Sprawdzenie, czy minęło więcej niż 90 dni od ostatniej konserwacji
233    const dzisiaj = new Date();
234    const różnicaMilisekundy = dzisiaj.getTime() - this.ostatniaKonserwacja.getTime();
235    const różnicaDni = różnicaMilisekundy / (1000 * 60 * 60 * 24);
236
237    if (różnicaDni > 90 || this.bateriaPoziom < 20) {
238      return "wymagana";
239    }
240
241    return "aktualna";
242  }
243
244  // Własne metody
245  przypiszDinozaura(idDinozaura: string): void {
246    this.przypisanyDinozaur = idDinozaura;
247    this.dodajPomiar("przypisanyDinozaur", idDinozaura);
248    console.log(`Urządzenie ${this.id}: Przypisano do dinozaura ${idDinozaura}`);
249  }
250
251  private zmniejszBaterię(wartość: number): void {
252    this.bateriaPoziom = Math.max(0, this.bateriaPoziom - wartość);
253    this.parametryMonitorowane.set("bateria", this.bateriaPoziom);
254
255    // Jeśli bateria jest krytycznie niska - zmień status
256    if (this.bateriaPoziom < 5 && this.status === "aktywny") {
257      this.aktualizujStatus("nieaktywny");
258      console.log(`Urządzenie ${this.id}: Krytyczny poziom baterii! Urządzenie wyłączone.`);
259    }
260  }
261}
262
263// Implementacja systemu zabezpieczeń wybiegu dinozaurów
264class OgrodzenieElektryczne implements PozycjaInwentarza, SystemBezpieczeństwa, Konserwowalny {
265  // PozycjaInwentarza
266  readonly id: string;
267  nazwa: string;
268  kategoria: string = "Ogrodzenie Elektryczne";
269  dataDodania: Date;
270  ostatniaAktualizacja: Date;
271  status: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany";
272
273  // SystemBezpieczeństwa
274  poziomZagrożenia: 1 | 2 | 3 | 4 | 5 = 1;
275  strefaBezpieczeństwa: string;
276  osobyUprawnione: string[] = [];
277
278  // Konserwowalny
279  wymaganyPoziomKonserwacji: number = 5; // Najwyższy poziom - kluczowe dla bezpieczeństwa
280  ostatniaKonserwacja: Date | null = null;
281  planowanaKonserwacja: Date | null = null;
282  historiaKonserwacji: Array<{ data: Date, opis: string, wykonawca: string }> = [];
283
284  // Własne właściwości
285  private napięcie: number; // w woltach
286  private długośćOgrodzenia: number; // w metrach
287  private czujnikiRuchu: boolean;
288
289  constructor(
290    id: string,
291    nazwa: string,
292    strefaBezpieczeństwa: string,
293    długośćOgrodzenia: number,
294    napięcie: number = 10000,
295    czujnikiRuchu: boolean = true
296  ) {
297    this.id = id;
298    this.nazwa = nazwa;
299    this.strefaBezpieczeństwa = strefaBezpieczeństwa;
300    this.długośćOgrodzenia = długośćOgrodzenia;
301    this.napięcie = napięcie;
302    this.czujnikiRuchu = czujnikiRuchu;
303
304    this.dataDodania = new Date();
305    this.ostatniaAktualizacja = new Date();
306    this.status = "nieaktywny";
307
308    // Dodanie administratorów systemu jako osób uprawnionych
309    this.osobyUprawnione.push("ADMIN-001", "SEC-CHIEF", "TECH-HEAD");
310  }
311
312  // Implementacja PozycjaInwentarza
313  aktualizujStatus(nowyStatus: "aktywny" | "nieaktywny" | "w naprawie" | "wycofany"): void {
314    // Jeśli przełączamy z nieaktywnego na aktywny
315    if (this.status !== "aktywny" && nowyStatus === "aktywny") {
316      // Sprawdź, czy konserwacja jest wymagana
317      if (this.sprawdźStatusKonserwacji() === "wymagana") {
318        console.log(`Ogrodzenie ${this.id}: OSTRZEŻENIE - Aktywacja bez wymaganej konserwacji!`);
319      }
320    }
321
322    this.status = nowyStatus;
323    this.ostatniaAktualizacja = new Date();
324
325    // Automatycznie dostosuj napięcie w zależności od statusu
326    if (nowyStatus === "aktywny") {
327      console.log(`Ogrodzenie ${this.id}: Aktywowano z napięciem ${this.napięcie}V`);
328    } else if (nowyStatus === "nieaktywny" || nowyStatus === "w naprawie") {
329      this.napięcie = 0;
330      console.log(`Ogrodzenie ${this.id}: Dezaktywowano - napięcie 0V`);
331    }
332  }
333
334  pobierzInformacje(): Record<string, any> {
335    return {
336      id: this.id,
337      nazwa: this.nazwa,
338      kategoria: this.kategoria,
339      status: this.status,
340      strefaBezpieczeństwa: this.strefaBezpieczeństwa,
341      długośćOgrodzenia: this.długośćOgrodzenia,
342      napięcie: this.napięcie,
343      czujnikiRuchu: this.czujnikiRuchu,
344      poziomZagrożenia: this.poziomZagrożenia
345    };
346  }
347
348  // Implementacja SystemBezpieczeństwa
349  podnieśPoziomZagrożenia(): void {
350    if (this.poziomZagrożenia < 5) {
351      this.poziomZagrożenia = (this.poziomZagrożenia + 1) as 1 | 2 | 3 | 4 | 5;
352
353      // Automatyczne zwiększenie napięcia
354      this.zwiększNapięcie(2000 * this.poziomZagrożenia);
355
356      console.log(`Ogrodzenie ${this.id}: Podniesiono poziom zagrożenia do ${this.poziomZagrożenia}. Nowe napięcie: ${this.napięcie}V`);
357    } else {
358      console.log(`Ogrodzenie ${this.id}: Poziom zagrożenia już na maksymalnym poziomie 5`);
359    }
360  }
361
362  obniżPoziomZagrożenia(): void {
363    if (this.poziomZagrożenia > 1) {
364      this.poziomZagrożenia = (this.poziomZagrożenia - 1) as 1 | 2 | 3 | 4 | 5;
365
366      // Automatyczne zmniejszenie napięcia (ale nadal aktywne)
367      const noweNapięcie = 10000 + (2000 * (this.poziomZagrożenia - 1));
368      this.napięcie = noweNapięcie;
369
370      console.log(`Ogrodzenie ${this.id}: Obniżono poziom zagrożenia do ${this.poziomZagrożenia}. Nowe napięcie: ${this.napięcie}V`);
371    } else {
372      console.log(`Ogrodzenie ${this.id}: Poziom zagrożenia już na minimalnym poziomie 1`);
373    }
374  }
375
376  sprawdźUprawnienia(idPracownika: string): boolean {
377    return this.osobyUprawnione.includes(idPracownika);
378  }
379
380  reagujNaZagrożenie(opis: string): void {
381    console.log(`Ogrodzenie ${this.id}: ALERT! ${opis}`);
382
383    // Maksymalny poziom zagrożenia
384    this.poziomZagrożenia = 5;
385
386    // Maksymalne napięcie
387    this.napięcie = 30000;
388
389    console.log(`Ogrodzenie ${this.id}: Poziom zagrożenia 5. Napięcie maksymalne: ${this.napięcie}V`);
390  }
391
392  // Implementacja Konserwowalny
393  zaplanujKonserwację(data: Date): void {
394    this.planowanaKonserwacja = data;
395    console.log(`Ogrodzenie ${this.id}: Zaplanowano konserwację na ${data.toLocaleDateString()}`);
396  }
397
398  wykonajKonserwację(opis: string, wykonawca: string): void {
399    const data = new Date();
400
401    // Zabezpieczenie - tymczasowa dezaktywacja ogrodzenia podczas konserwacji
402    const poprzedniStatus = this.status;
403    this.aktualizujStatus("w naprawie");
404
405    // Dodanie do historii konserwacji
406    this.historiaKonserwacji.push({ data, opis, wykonawca });
407
408    // Aktualizacja daty ostatniej konserwacji
409    this.ostatniaKonserwacja = data;
410
411    // Resetowanie planowanej konserwacji
412    this.planowanaKonserwacja = null;
413
414    console.log(`Ogrodzenie ${this.id}: Wykonano konserwację przez ${wykonawca}: ${opis}`);
415
416    // Przywrócenie poprzedniego statusu
417    this.aktualizujStatus(poprzedniStatus);
418  }
419
420  sprawdźStatusKonserwacji(): "wymagana" | "zaplanowana" | "aktualna" {
421    // Brak wcześniejszej konserwacji - wymagana
422    if (!this.ostatniaKonserwacja) {
423      return "wymagana";
424    }
425
426    // Zaplanowana konserwacja w przyszłości
427    if (this.planowanaKonserwacja && this.planowanaKonserwacja > new Date()) {
428      return "zaplanowana";
429    }
430
431    // Sprawdzenie, czy minęło więcej niż 30 dni od ostatniej konserwacji (krótszy okres dla ogrodzeń)
432    const dzisiaj = new Date();
433    const różnicaMilisekundy = dzisiaj.getTime() - this.ostatniaKonserwacja.getTime();
434    const różnicaDni = różnicaMilisekundy / (1000 * 60 * 60 * 24);
435
436    if (różnicaDni > 30) {
437      return "wymagana";
438    }
439
440    return "aktualna";
441  }
442
443  // Własne metody
444  dodajOsobęUprawnioną(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  usuńOsobęUprawnioną(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}: Usunięto uprawnienia dla pracownika ${idPracownika}`);
456    }
457  }
458
459  zwiększNapięcie(wartość: number): void {
460    if (this.status !== "aktywny") {
461      console.log(`Ogrodzenie ${this.id}: Nie można zmienić napięcia - ogrodzenie nie jest aktywne`);
462      return;
463    }
464
465    this.napięcie += wartość;
466    console.log(`Ogrodzenie ${this.id}: Zwiększono napięcie do ${this.napięcie}V`);
467  }
468
469  zmniejszNapięcie(wartość: number): void {
470    if (this.status !== "aktywny") {
471      console.log(`Ogrodzenie ${this.id}: Nie można zmienić napięcia - ogrodzenie nie jest aktywne`);
472      return;
473    }
474
475    this.napięcie = Math.max(0, this.napięcie - wartość);
476    console.log(`Ogrodzenie ${this.id}: Zmniejszono napięcie do ${this.napięcie}V`);
477  }
478}
479
480// Demonstrujemy użycie naszych implementacji
481function demonstrujSystemParku() {
482  console.log("=== DEMONSTRACJA SYSTEMU ZARZĄDZANIA PARKIEM JURAJSKIM ===");
483
484  // Utworzenie urządzenia śledzącego
485  console.log("
486--- Inicjalizacja urządzenia śledzącego ---");
487  const urządzenieŚledzące = new UrządzenieŚledząceT3000("TRACK-001", "Lokalizator T-Rex #1");
488  urządzenieŚledzące.aktualizujStatus("aktywny");
489  urządzenieŚledzące.rozpocznijMonitoring();
490  urządzenieŚledzące.przypiszDinozaura("TREX-001");
491
492  // Symulacja śledzenia lokalizacji
493  urządzenieŚledzące.aktualizujLokalizację(135.42, 86.21);
494  urządzenieŚledzące.aktualizujLokalizację(135.50, 86.35);
495  urządzenieŚledzące.aktualizujLokalizację(135.62, 86.40);
496
497  // Utworzenie ogrodzenia
498  console.log("
499--- Inicjalizacja ogrodzenia ---");
500  const ogrodzenie = new OgrodzenieElektryczne(
501    "FENCE-002",
502    "Ogrodzenie wybiegu T-Rex",
503    "T-REX-A",
504    1200, // 1200 metrów długości
505    15000, // 15000V
506    true // z czujnikami ruchu
507  );
508
509  // Dodanie uprawnień dla pracownika
510  ogrodzenie.dodajOsobęUprawnioną("EMP-047"); // Owen Grady
511
512  // Aktywacja ogrodzenia
513  ogrodzenie.aktualizujStatus("aktywny");
514
515  // Przeprowadzenie konserwacji urządzenia
516  console.log("
517--- Konserwacja urządzenia ---");
518  urządzenieŚledzące.zaplanujKonserwację(new Date("2023-06-15"));
519  urządzenieŚledzące.wykonajKonserwację("Wymiana baterii i kalibracja GPS", "Tech Ivan");
520
521  // Symulacja alarmu
522  console.log("
523--- Symulacja incydentu ---");
524  console.log("T-Rex próbuje przedostać się przez ogrodzenie!");
525
526  // Urządzenie śledzące rejestruje gwałtowny ruch
527  urządzenieŚledzące.aktualizujLokalizację(135.90, 86.70);
528  urządzenieŚledzące.aktualizujLokalizację(136.10, 86.90);
529  urządzenieŚledzące.dodajPomiar("przyspieszenie", 12.5);
530
531  // Ogrodzenie reaguje na zagrożenie
532  ogrodzenie.reagujNaZagrożenie("Wykryto próbę ucieczki T-Rexa");
533
534  // Sprawdzenia statusu po incydencie
535  console.log("
536--- Raport po incydencie ---");
537  console.log("Ostatnia lokalizacja dinozaura:", urządzenieŚledzące.pobierzOstatniaLokalizację());
538  console.log("Status ogrodzenia:", ogrodzenie.pobierzInformacje());
539
540  // Powrót do normalnego trybu
541  console.log("
542--- Normalizacja sytuacji ---");
543  ogrodzenie.obniżPoziomZagrożenia();
544  ogrodzenie.obniżPoziomZagrożenia();
545
546  console.log("
547=== KONIEC DEMONSTRACJI ===");
548}
549
550// Uruchomienie demonstracji
551demonstrujSystemParku();

Podsumowanie

W tym ćwiczeniu poznaliśmy interfejsy TypeScript - potężne narzędzie do definiowania kontraktów i tworzenia spójnych struktur w kodzie.

Najważniejsze cechy interfejsów:

  1. Definiują kształt obiektów - określają, jakie właściwości i metody muszą posiadać obiekty danego typu
  2. Wsparcie dla polimorfizmu - różne klasy mogą implementować ten sam interfejs na różne sposoby
  3. Luźne powiązanie - skupiają się na tym, co obiekty robią, a nie jak są zaimplementowane
  4. Elastyczność - właściwości opcjonalne, tylko do odczytu, indeksy, itp.
  5. Rozszerzalność - interfejsy mogą dziedziczyć po innych interfejsach

Jak Claire Dearing podsumowałaby: "W Parku Jurajskim, podobnie jak w dobrej architekturze oprogramowania, każdy ma swoją rolę i obowiązki. Interfejsy pozwalają nam jasno zdefiniować te role, zapewniając, że wszystkie systemy dobrze ze sobą współpracują - bez niespodzianek. A uwierzcie mi, niespodzianki w parku pełnym dinozaurów to ostatnia rzecz, jakiej potrzebujemy."

Ir a CodeWorlds