Kurs JavaScript i TypeScript · Moduł 6: Podstawy TypeScript

Definicja interfejsów

11 min czytania
W tej lekcji11

Witaj z powrotem w Parku Jurajskim! Dziś, pod czujnym okiem Dr. Henry Wu, będziemy zgłębiać fundamentalną koncepcję TypeScript, która jest równie kluczowa dla organizacji kodu, jak protokoły bezpieczeństwa są dla prawidłowego funkcjonowania parku - interfejsy.

Czym są interfejsy w TypeScript?

Interfejsy w TypeScript definiują kontrakty w kodzie oraz opisują strukturę obiektów. Są one jak szczegółowe protokoły, które określają, co powinno zawierać dany obiekt. W kontekście Parku Jurajskiego, interfejsy można porównać do standardów bezpieczeństwa i protokołów obsługi dla różnych gatunków dinozaurów - każdy gatunek ma swoje specyficzne wymagania i cechy.

Podstawowa składnia interfejsu

Interfejs definiujemy za pomocą słowa kluczowego interface:

1interface Dinozaur {
2  id: number;
3  nazwa: string;
4  gatunek: string;
5  wiek: number;
6  waga: number;
7}
8
9// Tworzymy obiekt, który musi spełniać wymagania interfejsu
10const rex: Dinozaur = {
11  id: 1,
12  nazwa: "Rex",
13  gatunek: "Tyrannosaurus",
14  wiek: 7,
15  waga: 8000
16};
17
18// Poniższy kod spowodowałby błąd, ponieważ brakuje wymaganych właściwości
19// const raptor: Dinozaur = {
20//   id: 2,
21//   nazwa: "Blue"
22// };

Właściwości opcjonalne

Niektóre właściwości w interfejsie mogą być opcjonalne, oznaczane przez dodanie znaku zapytania (?). To jak oznaczenie opcjonalnych procedur obsługi dla poszczególnych dinozaurów.

1interface OkazDinozaura {
2  id: number;
3  nazwa: string;
4  gatunek: string;
5  tagRFID?: string;        // Opcjonalny tag RFID
6  ostatnieBadanie?: Date;  // Opcjonalna data ostatniego badania
7  notatki?: string;        // Opcjonalne notatki badawcze
8}
9
10// Obiekt spełniający interfejs, ale bez wszystkich opcjonalnych właściwości
11const okaz1: OkazDinozaura = {
12  id: 101,
13  nazwa: "Specimen A",
14  gatunek: "Velociraptor"
15};
16
17// Obiekt z niektórymi opcjonalnymi właściwościami
18const okaz2: OkazDinozaura = {
19  id: 102,
20  nazwa: "Specimen B",
21  gatunek: "Dilophosaurus",
22  tagRFID: "DILO-102",
23  notatki: "Wykazuje agresywne zachowania w obecności nowych obiektów"
24};

Tylko do odczytu (readonly)

Niektóre właściwości powinny być modyfikowane tylko przy tworzeniu obiektu. W TypeScript możemy to osiągnąć używając modyfikatora readonly. To jak procedury bezpieczeństwa, które raz ustalone, nie mogą być zmieniane podczas normalnej operacji.

1interface KodGenetyczny {
2  readonly id: string;
3  readonly sekwencja: string;
4  readonly gatunekDawca: string;
5  modyfikacje: string[];
6  dataIzolacji: Date;
7}
8
9// Tworzymy kod genetyczny
10const kodTRex: KodGenetyczny = {
11  id: "GEN-TREX-001",
12  sekwencja: "ACGTTGCAAGCTA...",
13  gatunekDawca: "Żaba zachodnioafrykańska",
14  modyfikacje: ["Uzupełnienie luk DNA", "Korekta genów agresji"],
15  dataIzolacji: new Date("2022-05-15")
16};
17
18// Możemy dodawać nowe modyfikacje
19kodTRex.modyfikacje.push("Wzmocnienie odporności immunologicznej");
20
21// Ale nie możemy zmienić wartości pól readonly
22// kodTRex.id = "GEN-TREX-002"; // Błąd: Cannot assign to 'id' because it is a read-only property
23// kodTRex.sekwencja = "GCTA..."; // Błąd: Cannot assign to 'sekwencja' because it is a read-only property

Interfejsy funkcji

Interfejsy mogą również definiować typy funkcji, co jest przydatne przy opisywaniu funkcji zwrotnych lub przekazywanych jako parametry. W kontekście parku, to jak definiowanie standardowych procedur obsługi różnych sytuacji.

1// Interfejs funkcji bezpieczeństwa
2interface SprawdzenieBezpieczeństwa {
3  (poziomZagrożenia: number, strefaParku: string): boolean;
4}
5
6// Implementacja funkcji zgodnej z interfejsem
7const sprawdźDostęp: SprawdzenieBezpieczeństwa = function(poziom, strefa) {
8  // Logika sprawdzania dostępu
9  if (strefa === "Wybieg T-Rex" && poziom < 5) {
10    return false; // Odmowa dostępu do strefy T-Rex dla pracowników poniżej poziomu 5
11  }
12
13  if (strefa.includes("Laboratorium") && poziom < 4) {
14    return false; // Odmowa dostępu do laboratoriów dla pracowników poniżej poziomu 4
15  }
16
17  return true; // Dostęp dozwolony dla pozostałych przypadków
18};
19
20// Użycie funkcji
21console.log(sprawdźDostęp(3, "Wybieg T-Rex")); // false
22console.log(sprawdźDostęp(5, "Wybieg T-Rex")); // true
23console.log(sprawdźDostęp(3, "Obszar publiczny")); // true

Indeksowalne typy

Interfejsy mogą definiować typy indeksowalne - użyteczne, gdy mamy obiekty z dynamiczną liczbą właściwości tego samego typu. W kontekście parku, może to być baza danych dinozaurów indeksowana po ich identyfikatorach.

1// Interfejs dla bazy danych dinozaurów
2interface BazaDanychDinozaurów {
3  [id: string]: {
4    nazwa: string;
5    gatunek: string;
6    wiek: number;
7    zdrowie: "dobre" | "wymaga uwagi" | "krytyczne";
8  };
9}
10
11// Tworzenie bazy danych
12const bazaDanych: BazaDanychDinozaurów = {
13  "TREX-001": {
14    nazwa: "Rex",
15    gatunek: "Tyrannosaurus",
16    wiek: 7,
17    zdrowie: "dobre"
18  },
19  "VELOC-001": {
20    nazwa: "Blue",
21    gatunek: "Velociraptor",
22    wiek: 4,
23    zdrowie: "dobre"
24  },
25  "TRIC-003": {
26    nazwa: "Trike",
27    gatunek: "Triceratops",
28    wiek: 10,
29    zdrowie: "wymaga uwagi"
30  }
31};
32
33// Dostęp do danych
34console.log(bazaDanych["VELOC-001"].nazwa); // "Blue"
35
36// Dodawanie nowego wpisu
37bazaDanych["STEGO-002"] = {
38  nazwa: "Spike",
39  gatunek: "Stegosaurus",
40  wiek: 6,
41  zdrowie: "dobre"
42};

Interfejsy klas

Interfejsy mogą również definiować kontrakty, które klasy muszą implementować. To jak standardy operacyjne, które każdy departament w parku musi spełniać.

1// Interfejs dla systemów bezpieczeństwa
2interface SystemBezpieczeństwa {
3  nazwa: string;
4  czyAktywny: boolean;
5  poziomBezpieczeństwa: number;
6
7  aktywuj(): void;
8  dezaktywuj(): void;
9  sprawdźStan(): string;
10}
11
12// Klasa implementująca interfejs
13class SystemElektronicznychOgrodzeń implements SystemBezpieczeństwa {
14  nazwa: string;
15  czyAktywny: boolean;
16  poziomBezpieczeństwa: number;
17  napięcie: number; // Dodatkowa właściwość specyficzna dla tej klasy
18
19  constructor(nazwa: string, poziomBezpieczeństwa: number, napięcie: number) {
20    this.nazwa = nazwa;
21    this.czyAktywny = false; // Początkowo nieaktywny
22    this.poziomBezpieczeństwa = poziomBezpieczeństwa;
23    this.napięcie = napięcie;
24  }
25
26  aktywuj(): void {
27    this.czyAktywny = true;
28    console.log(`System ${this.nazwa} został aktywowany. Napięcie: ${this.napięcie}V`);
29  }
30
31  dezaktywuj(): void {
32    this.czyAktywny = false;
33    console.log(`System ${this.nazwa} został dezaktywowany.`);
34  }
35
36  sprawdźStan(): string {
37    return this.czyAktywny
38      ? `${this.nazwa}: AKTYWNY (Poziom ${this.poziomBezpieczeństwa})`
39      : `${this.nazwa}: NIEAKTYWNY`;
40  }
41
42  // Metoda specyficzna dla tej klasy
43  zwiększNapięcie(o: number): void {
44    this.napięcie += o;
45    console.log(`Zwiększono napięcie do ${this.napięcie}V`);
46  }
47}
48
49// Użycie klasy
50const ogrodzenieTRex = new SystemElektronicznychOgrodzeń("Ogrodzenie T-Rex", 5, 10000);
51ogrodzenieTRex.aktywuj();
52console.log(ogrodzenieTRex.sprawdźStan());
53ogrodzenieTRex.zwiększNapięcie(2000);

Rozszerzanie interfejsów

Interfejsy mogą rozszerzać inne interfejsy, podobnie jak w biologii, gdzie gatunki dzielą wspólne cechy, ale mają też swoje unikalne właściwości.

1// Podstawowy interfejs dla wszystkich zwierząt w parku
2interface Zwierzę {
3  id: number;
4  gatunek: string;
5  wiek: number;
6  waga: number;
7}
8
9// Interfejs dla dinozaurów rozszerzający interfejs Zwierzę
10interface Dinozaur extends Zwierzę {
11  era: string; // np. "Kreda", "Jura"
12  jestDrapieżnikiem: boolean;
13  dietaGłówna: string[];
14}
15
16// Interfejs dla dinozaurów drapieżnych rozszerzający interfejs Dinozaur
17interface DrapieżnyDinozaur extends Dinozaur {
18  siłaUgryzienia: number;  // w PSI
19  prędkośćBiegu: number;   // w km/h
20  zachowaniePodczasPolowania: string[];
21}
22
23// Tworzenie obiektu zgodnego z interfejsem DrapieżnyDinozaur
24const velociraptor: DrapieżnyDinozaur = {
25  id: 101,
26  gatunek: "Velociraptor",
27  wiek: 4,
28  waga: 70,
29  era: "Kreda",
30  jestDrapieżnikiem: true,
31  dietaGłówna: ["małe ssaki", "jaszczurki", "inne małe dinozaury"],
32  siłaUgryzienia: 1000,
33  prędkośćBiegu: 60,
34  zachowaniePodczasPolowania: ["polowanie w stadzie", "zasadzka", "pościg"]
35};

Łączenie deklaracji interfejsów

W TypeScript możliwe jest definiowanie tego samego interfejsu wielokrotnie - deklaracje zostaną połączone. To przydatne, gdy chcemy dodać nowe właściwości do istniejącego interfejsu.

1// Początkowa definicja protokołu bezpieczeństwa
2interface ProtokółBezpieczeństwa {
3  nazwa: string;
4  poziomZagrożenia: number;
5  proceduraEwakuacji: string;
6}
7
8// Rozszerzenie protokołu o dodatkowe właściwości
9interface ProtokółBezpieczeństwa {
10  personelOdpowiedzialny: string[];
11  czasReakcji: number; // w minutach
12}
13
14// Obiekt musi zawierać wszystkie właściwości z obu deklaracji
15const protokółAlfa: ProtokółBezpieczeństwa = {
16  nazwa: "Protokół Alfa",
17  poziomZagrożenia: 3,
18  proceduraEwakuacji: "Ewakuacja w kierunku głównego centrum kontroli",
19  personelOdpowiedzialny: ["Dr. Wu", "Owen Grady", "Claire Dearing"],
20  czasReakcji: 15
21};

Interfejsy vs typy

TypeScript oferuje również słowo kluczowe type do definiowania typów. Choć często interfejsy i typy mogą być używane zamiennie, istnieją między nimi różnice:

1// Definicja za pomocą interface
2interface PlanEwakuacji {
3  trasa: string[];
4  czasSzacunkowy: number;
5  punktyZbiórek: string[];
6}
7
8// Równoważna definicja za pomocą type
9type PlanEwakuacjiType = {
10  trasa: string[];
11  czasSzacunkowy: number;
12  punktyZbiórek: string[];
13};
14
15// Oba działają podobnie przy podstawowym użyciu
16const planA: PlanEwakuacji = {
17  trasa: ["Centrum operacyjne", "Main Street", "Hala ewakuacyjna"],
18  czasSzacunkowy: 25,
19  punktyZbiórek: ["Centrum operacyjne", "Hala ewakuacyjna"]
20};
21
22const planB: PlanEwakuacjiType = {
23  trasa: ["Wybieg Gallimimus", "Innovation Center", "Hala ewakuacyjna"],
24  czasSzacunkowy: 30,
25  punktyZbiórek: ["Innovation Center", "Hala ewakuacyjna"]
26};

Główne różnice między interface a type:

  1. Rozszerzalność:

    • Interfejsy można rozszerzać przez wielokrotne deklaracje o tej samej nazwie
    • Typy nie mogą być "otwarte" - raz zadeklarowany typ nie może być rozszerzony
  2. Złożone typy:

    • Typy mogą reprezentować bezpośrednio typy proste, unie, przecięcia
    • Interfejsy są bardziej ukierunkowane na definiowanie kształtu obiektów
1// Możliwe tylko z typami
2type IDDinozaura = string | number;
3type KoordynatyWybiegu = [number, number, string]; // Tuple
4type StatusDinozaura = "aktywny" | "odpoczywający" | "śpiący"; // Union of literals
5
6// Bardziej naturalne z interfejsami
7interface Procedura {
8  nazwa: string;
9  kroki: string[];
10  czasWykonania: number;
11  personelOdpowiedzialny: string[];
12}

Praktyczne zastosowanie interfejsów

Przyjrzyjmy się, jak możemy wykorzystać interfejsy do modelowania systemu zarządzania Parkiem Jurajskim:

1// Podstawowe interfejsy
2interface OsobaWParku {
3  id: string;
4  imię: string;
5  nazwisko: string;
6  poziomDostępu: 1 | 2 | 3 | 4 | 5;
7  dataZatrudnienia: Date;
8}
9
10interface Lokalizacja {
11  id: string;
12  nazwa: string;
13  typ: "wybieg" | "laboratorium" | "centrum operacyjne" | "strefa publiczna";
14  współrzędne: [number, number]; // [szerokość, długość]
15  wymaganyPoziomDostępu: 1 | 2 | 3 | 4 | 5;
16}
17
18interface GatunekDinozaura {
19  nazwa: string;
20  eraGeologiczna: string;
21  typ: "roślinożerca" | "drapieżnik" | "wszystkożerca";
22  przeciętnaWaga: number;
23  przeciętnyWiek: number;
24  poziomNiebezpieczeństwa: 1 | 2 | 3 | 4 | 5;
25  wymaganiaDiety: string[];
26  wymaganiaHabitatu: string[];
27}
28
29interface OkazDinozaura {
30  id: string;
31  gatunek: string;
32  imię?: string;
33  wiek: number;
34  waga: number;
35  płeć: "samiec" | "samica";
36  lokalizacja: string; // ID lokalizacji
37  stanZdrowia: "doskonały" | "dobry" | "wymaga uwagi" | "chory" | "krytyczny";
38  ostatnieBadanie: Date;
39  tagRFID: string;
40  notatki?: string;
41}
42
43// Interfejsy dla systemów zarządzania
44interface SystemŚledzeniaOkazów {
45  pobierzPozycję(idOkazu: string): [number, number];
46  śledźRuch(idOkazu: string, czasŚledzenia: number): [number, number][];
47  sprawdźCzyWStrefie(idOkazu: string, idStrefy: string): boolean;
48  alertGdyPozaStrefą(idOkazu: string, callback: (okaz: OkazDinozaura) => void): void;
49}
50
51interface SystemZarządzaniaPracownikami {
52  dodajPracownika(pracownik: OsobaWParku): void;
53  usuńPracownika(id: string): boolean;
54  przyznajDostęp(idPracownika: string, poziom: 1 | 2 | 3 | 4 | 5): boolean;
55  sprawdźObecnośćWStrefie(idPracownika: string, idStrefy: string): boolean;
56}
57
58// Przykład implementacji
59class SystemZarządzaniaParkiem {
60  private pracownicy: OsobaWParku[] = [];
61  private lokalizacje: Lokalizacja[] = [];
62  private gatunki: GatunekDinozaura[] = [];
63  private okazy: OkazDinozaura[] = [];
64
65  // Metody dla pracowników
66  dodajPracownika(pracownik: OsobaWParku): void {
67    this.pracownicy.push(pracownik);
68    console.log(`Dodano pracownika: ${pracownik.imię} ${pracownik.nazwisko}`);
69  }
70
71  // Metody dla lokalizacji
72  dodajLokalizację(lokalizacja: Lokalizacja): void {
73    this.lokalizacje.push(lokalizacja);
74    console.log(`Dodano lokalizację: ${lokalizacja.nazwa}`);
75  }
76
77  // Metody dla gatunków
78  dodajGatunek(gatunek: GatunekDinozaura): void {
79    this.gatunki.push(gatunek);
80    console.log(`Dodano gatunek: ${gatunek.nazwa}`);
81  }
82
83  // Metody dla okazów
84  dodajOkaz(okaz: OkazDinozaura): void {
85    this.okazy.push(okaz);
86    console.log(`Dodano okaz: ${okaz.id} (${okaz.gatunek})`);
87  }
88
89  // Metody zarządzania
90  sprawdźDostęp(idPracownika: string, idLokalizacji: string): boolean {
91    const pracownik = this.pracownicy.find(p => p.id === idPracownika);
92    const lokalizacja = this.lokalizacje.find(l => l.id === idLokalizacji);
93
94    if (!pracownik || !lokalizacja) return false;
95
96    return pracownik.poziomDostępu >= lokalizacja.wymaganyPoziomDostępu;
97  }
98
99  znajdźOkazyGatunku(nazwaGatunku: string): OkazDinozaura[] {
100    return this.okazy.filter(o => o.gatunek === nazwaGatunku);
101  }
102
103  znajdźOkazyWStrefie(idStrefy: string): OkazDinozaura[] {
104    return this.okazy.filter(o => o.lokalizacja === idStrefy);
105  }
106
107  generujRaportStanuParku(): {
108    liczbaPracowników: number;
109    liczbaLokalizacji: number;
110    liczbaGatunków: number;
111    liczbaOkazów: number;
112    stanZdrowiaOkazów: {
113      doskonały: number;
114      dobry: number;
115      wymagaUwagi: number;
116      chory: number;
117      krytyczny: number;
118    }
119  } {
120    // Analiza stanu zdrowia wszystkich okazów
121    const stanZdrowia = {
122      doskonały: 0,
123      dobry: 0,
124      wymagaUwagi: 0,
125      chory: 0,
126      krytyczny: 0
127    };
128
129    this.okazy.forEach(okaz => {
130      if (okaz.stanZdrowia === "doskonały") stanZdrowia.doskonały++;
131      else if (okaz.stanZdrowia === "dobry") stanZdrowia.dobry++;
132      else if (okaz.stanZdrowia === "wymaga uwagi") stanZdrowia.wymagaUwagi++;
133      else if (okaz.stanZdrowia === "chory") stanZdrowia.chory++;
134      else if (okaz.stanZdrowia === "krytyczny") stanZdrowia.krytyczny++;
135    });
136
137    return {
138      liczbaPracowników: this.pracownicy.length,
139      liczbaLokalizacji: this.lokalizacje.length,
140      liczbaGatunków: this.gatunki.length,
141      liczbaOkazów: this.okazy.length,
142      stanZdrowiaOkazów: stanZdrowia
143    };
144  }
145}
146
147// Przykład użycia
148const parkJurajski = new SystemZarządzaniaParkiem();
149
150// Dodanie lokalizacji
151parkJurajski.dodajLokalizację({
152  id: "LOC-001",
153  nazwa: "Wybieg T-Rex",
154  typ: "wybieg",
155  współrzędne: [23.45, -67.89],
156  wymaganyPoziomDostępu: 4
157});
158
159// Dodanie gatunku
160parkJurajski.dodajGatunek({
161  nazwa: "Tyrannosaurus Rex",
162  eraGeologiczna: "Kreda",
163  typ: "drapieżnik",
164  przeciętnaWaga: 7000,
165  przeciętnyWiek: 30,
166  poziomNiebezpieczeństwa: 5,
167  wymaganiaDiety: ["duże ilości mięsa", "uzupełnianie wapnia"],
168  wymaganiaHabitatu: ["duży obszar", "naturalna roślinność", "dostęp do wody"]
169});
170
171// Dodanie pracownika
172parkJurajski.dodajPracownika({
173  id: "EMP-001",
174  imię: "Owen",
175  nazwisko: "Grady",
176  poziomDostępu: 5,
177  dataZatrudnienia: new Date("2010-06-15")
178});
179
180// Dodanie okazu
181parkJurajski.dodajOkaz({
182  id: "DINO-001",
183  gatunek: "Tyrannosaurus Rex",
184  imię: "Rexy",
185  wiek: 7,
186  waga: 7500,
187  płeć: "samica",
188  lokalizacja: "LOC-001",
189  stanZdrowia: "dobry",
190  ostatnieBadanie: new Date("2023-03-20"),
191  tagRFID: "RFID-TREX-001"
192});
193
194// Generowanie raportu
195const raport = parkJurajski.generujRaportStanuParku();
196console.log("Raport stanu Parku Jurajskiego:");
197console.log(`Liczba pracowników: ${raport.liczbaPracowników}`);
198console.log(`Liczba lokalizacji: ${raport.liczbaLokalizacji}`);
199console.log(`Liczba gatunków: ${raport.liczbaGatunków}`);
200console.log(`Liczba okazów: ${raport.liczbaOkazów}`);
201console.log("Stan zdrowia okazów:", raport.stanZdrowiaOkazów);

Podsumowanie

Interfejsy w TypeScript są potężnym narzędziem do modelowania struktur danych i definiowania kontraktów w kodzie. W kontekście zarządzania Parkiem Jurajskim, pozwalają nam precyzyjnie określić, jak powinny wyglądać różne systemy, procedury i elementy parku.

Cecha interfejsuZastosowanie w Parku Jurajskim
Podstawowa definicjaStruktury danych dla dinozaurów, pracowników, lokalizacji
Właściwości opcjonalneDodatkowe informacje, które mogą, ale nie muszą być dostępne
Właściwości readonlyDane, które nie powinny być zmieniane, np. ID genetyczne
Interfejsy funkcjiDefinicje procedur bezpieczeństwa i protokołów
Typy indeksowalneDynamiczne bazy danych dinozaurów i zasobów
Interfejsy klasStandardy dla systemów bezpieczeństwa i monitoringu
Rozszerzanie interfejsówDziedziczenie cech między gatunkami dinozaurów
Łączenie deklaracjiRozszerzanie protokołów o dodatkowe procedury

Dr. Wu podsumowuje: "Interfejsy w TypeScript, podobnie jak nasze protokoły w parku, zapewniają, że każdy element systemu działa zgodnie z oczekiwaniami. Dzięki nim możemy budować złożone systemy z pewnością, że poszczególne komponenty będą ze sobą współpracować - co jest absolutnie kluczowe, gdy mamy do czynienia z gatunkami, które mogą być niebezpieczne, jeśli coś pójdzie nie tak."

Kod do tej lekcji: index.ts
1// Interfejsy w Parku Jurajskim
2console.log("Interfejsy TypeScript\n");
3
4// ===========================================
5// 1. Podstawowe interfejsy
6// ===========================================
7console.log("=== PODSTAWOWE INTERFEJSY ===");
8
9// Prosty interfejs
10interface Dinosaur {
11  name: string;
12  species: string;
13  age: number;
14  weight: number;
15}
16
17const trex: Dinosaur = {
18  name: "Rexy",
19  species: "Tyrannosaurus Rex",
20  age: 5,
21  weight: 8000
22};
23
24console.log("T-Rex:", trex);
25
26// ===========================================
27// 2. Opcjonalne właściwości
28// ===========================================
29console.log("\n=== OPCJONALNE WŁAŚCIWOŚCI ===");
30
31interface DinosaurWithOptionals {
32  name: string;
33  species: string;
34  age: number;
35  nickname?: string; // opcjonalne
36  medicalNotes?: string; // opcjonalne
37}
38
39const blue: DinosaurWithOptionals = {
40  name: "Blue",
41  species: "Velociraptor",
42  age: 3,
43  nickname: "The Beta"
44  // medicalNotes pominięte - OK
45};
46
47console.log("Blue:", blue);
48console.log("Has medical notes:", blue.medicalNotes !== undefined);
49
50// ===========================================
51// 3. Readonly properties
52// ===========================================
53console.log("\n=== READONLY PROPERTIES ===");
54
55interface DinosaurWithReadonly {
56  readonly id: string; // nie można zmieniać
57  readonly dnaSequence: string; // nie można zmieniać
58  name: string; // można zmieniać
59  age: number; // można zmieniać
60}
61
62const dino: DinosaurWithReadonly = {
63  id: "DINO-001",
64  dnaSequence: "ACGT...",
65  name: "Rex",
66  age: 5
67};
68
69// dino.id = "DINO-002"; // Error: Cannot assign to 'id'
70dino.name = "Rexy"; // OK
71dino.age = 6; // OK
72
73console.log("Dinosaur:", dino);
74
75// ===========================================
76// 4. Metody w interfejsach
77// ===========================================
78console.log("\n=== METODY W INTERFEJSACH ===");
79
80interface DinosaurWithMethods {
81  name: string;
82  species: string;
83  energy: number;
84
85  // Metody
86  roar(): string;
87  eat(amount: number): void;
88  sleep(hours: number): void;
89  getStatus(): string;
90}
91
92class TyrannosaurusRex implements DinosaurWithMethods {
93  name: string;
94  species: string;
95  energy: number;
96
97  constructor(name: string) {
98    this.name = name;
99    this.species = "Tyrannosaurus Rex";
100    this.energy = 100;
101  }
102
103  roar(): string {
104    this.energy -= 5;
105    return `${this.name} roars: ROAAAAAR!`;
106  }
107
108  eat(amount: number): void {
109    this.energy += amount * 2;
110    if (this.energy > 100) this.energy = 100;
111    console.log(`${this.name} ate ${amount}kg. Energy: ${this.energy}`);
112  }
113
114  sleep(hours: number): void {
115    this.energy += hours * 10;
116    if (this.energy > 100) this.energy = 100;
117    console.log(`${this.name} slept ${hours}h. Energy: ${this.energy}`);
118  }
119
120  getStatus(): string {
121    if (this.energy > 70) return "Energetic";
122    if (this.energy > 30) return "Normal";
123    return "Tired";
124  }
125}
126
127const rexy = new TyrannosaurusRex("Rexy");
128console.log(rexy.roar());
129rexy.eat(50);
130console.log("Status:", rexy.getStatus());
131
132// ===========================================
133// 5. Index signatures
134// ===========================================
135console.log("\n=== INDEX SIGNATURES ===");
136
137// Dynamiczne klucze
138interface DinosaurCollection {
139  [id: string]: Dinosaur;
140}
141
142const collection: DinosaurCollection = {
143  "T-001": trex,
144  "V-001": blue
145};
146
147console.log("Collection:", collection);
148console.log("T-001:", collection["T-001"]);
149
150// Mieszane właściwości
151interface PaddockData {
152  id: string;
153  capacity: number;
154  [key: string]: any; // inne dynamiczne właściwości
155}
156
157const paddock: PaddockData = {
158  id: "P-01",
159  capacity: 10,
160  securityLevel: "high",
161  temperature: 25,
162  humidity: 60
163};
164
165console.log("Paddock:", paddock);
166
167// ===========================================
168// 6. Funkcje w interfejsach
169// ===========================================
170console.log("\n=== FUNKCJE W INTERFEJSACH ===");
171
172// Typ funkcji
173interface DinosaurFilter {
174  (dino: Dinosaur): boolean;
175}
176
177const carnivoreFilter: DinosaurFilter = (dino) => {
178  return dino.species.includes("saurus") && dino.weight > 1000;
179};
180
181console.log("Is T-Rex carnivore:", carnivoreFilter(trex));
182
183// Interface dla funkcji z właściwościami
184interface DinosaurValidator {
185  (dino: Dinosaur): boolean;
186  errorMessage: string;
187}
188
189const ageValidator: DinosaurValidator = (dino) => {
190  return dino.age >= 0 && dino.age <= 100;
191};
192ageValidator.errorMessage = "Age must be between 0 and 100";
193
194console.log("Validator:", ageValidator(trex));
195console.log("Error message:", ageValidator.errorMessage);
196
197// ===========================================
198// 7. Zagnieżdżone interfejsy
199// ===========================================
200console.log("\n=== ZAGNIEŻDŻONE INTERFEJSY ===");
201
202interface Location {
203  latitude: number;
204  longitude: number;
205  altitude: number;
206}
207
208interface Paddock {
209  id: string;
210  name: string;
211  location: Location; // zagnieżdżony interfejs
212  dimensions: {
213    width: number;
214    length: number;
215    height: number;
216  };
217  dinosaurs: Dinosaur[];
218}
219
220const trexPaddock: Paddock = {
221  id: "P-01",
222  name: "T-Rex Territory",
223  location: {
224    latitude: 22.3964,
225    longitude: -159.5261,
226    altitude: 50
227  },
228  dimensions: {
229    width: 1000,
230    length: 1500,
231    height: 50
232  },
233  dinosaurs: [trex]
234};
235
236console.log("Paddock:", trexPaddock);
237console.log("Location:", trexPaddock.location);
238
239// ===========================================
240// 8. Hybrid types
241// ===========================================
242console.log("\n=== HYBRID TYPES ===");
243
244interface Counter {
245  (start: number): string;
246  interval: number;
247  reset(): void;
248}
249
250function createCounter(): Counter {
251  let count = 0;
252
253  const counter = function(start: number) {
254    count = start;
255    return `Count: ${count}`;
256  } as Counter;
257
258  counter.interval = 1;
259  counter.reset = function() {
260    count = 0;
261  };
262
263  return counter;
264}
265
266const myCounter = createCounter();
267console.log(myCounter(5));
268console.log("Interval:", myCounter.interval);
269myCounter.reset();
270
271// ===========================================
272// 9. Podsumowanie
273// ===========================================
274console.log("\n=== PODSUMOWANIE ===");
275console.log("Podstawowe interfejsy - struktura obiektów");
276console.log("Opcjonalne właściwości - ?");
277console.log("Readonly - niezmienne właściwości");
278console.log("Metody - funkcje w interfejsach");
279console.log("Index signatures - dynamiczne klucze");
280console.log("Zagnieżdżone interfejsy - złożone struktury");
281
282console.log("\nInterfejsy opanowane!");

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym interfejs różni się od type alias w TypeScript?

  2. 2. Jak oznaczyć właściwość interfejsu jako opcjonalną w TypeScript?

Zadania praktyczne w grze

  • Edytor kodu

    Zdefiniuj interfejs Dinosaur z polami name: string, species: string, weight?: number i metodą roar(): void.

  • Układanie w poziomie

    Ułóż elementy definicji interfejsu TypeScript:

  • Klikanie w kolejności

    Ułóż elementy definicji pola readonly w interfejsie:

  • Układanie w pionie

    Uporządkuj koncepty interfejsów od najprostszych do najbardziej zaawansowanych:

Przydatne artykuły