Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Właściwości statyczne i abstrakcyjne

"W Parku Jurajskim mamy protokoły i standardy, które obowiązują wszystkie obiekty danej klasy," wyjaśnia Dr. Henry Wu, główny genetyk parku. "Na przykład, wszystkie nasze wybiegi dla dinozaurów mają tę samą procedurę bezpieczeństwa, niezależnie od tego, który konkretny wybieg rozpatrujemy. Z drugiej strony, każdy gatunek dinozaura ma swój własny, unikalny wzór DNA, ale wszyscy wiemy, że DNA musi istnieć - nawet jeśli szczegóły różnią się między gatunkami."

W programowaniu obiektowym TypeScript oferuje dwie potężne koncepcje, które odzwierciedlają te idee:

  • Właściwości i metody statyczne - współdzielone przez wszystkie instancje klasy
  • Właściwości i metody abstrakcyjne - muszą być zaimplementowane przez klasy dziedziczące

W tym ćwiczeniu zagłębimy się w te koncepcje, tworząc zaawansowany system zarządzania genetyką i protokołami bezpieczeństwa Parku Jurajskiego.

Właściwości i metody statyczne

Elementy statyczne w klasie należą do samej klasy, a nie do jej instancji. Oznacza to, że:

  • Istnieje tylko jedna kopia każdej właściwości statycznej, niezależnie od liczby utworzonych instancji
  • Dostęp do właściwości statycznych uzyskuje się poprzez nazwę klasy, a nie przez instancję
  • Metody statyczne mogą być wywoływane bezpośrednio na klasie

Podstawowe użycie elementów statycznych

Przyjrzyjmy się, jak można wykorzystać elementy statyczne w kontekście Parku Jurajskiego:

1class ProtokółBezpieczeństwa {
2  // Właściwość statyczna - współdzielona przez wszystkie instancje
3  static readonly POZIOM_KRYTYCZNY: number = 5;
4  static readonly POZIOM_WYSOKI: number = 4;
5  static readonly POZIOM_ŚREDNI: number = 3;
6  static readonly POZIOM_NISKI: number = 2;
7  static readonly POZIOM_MINIMALNY: number = 1;
8
9  // Statyczna właściwość przechowująca aktualny globalny poziom zagrożenia parku
10  private static _aktualnyPoziomZagrożenia: number = ProtokółBezpieczeństwa.POZIOM_NISKI;
11
12  // Właściwości instancji
13  private readonly obszar: string;
14  private lokalnyPoziomZagrożenia: number;
15
16  constructor(obszar: string) {
17    this.obszar = obszar;
18    this.lokalnyPoziomZagrożenia = ProtokółBezpieczeństwa._aktualnyPoziomZagrożenia;
19    console.log(`Utworzono protokół bezpieczeństwa dla obszaru: ${obszar}`);
20  }
21
22  // Metoda statyczna dostępna bezpośrednio na klasie
23  static podnieśGlobalnyPoziomZagrożenia(): void {
24    if (this._aktualnyPoziomZagrożenia < this.POZIOM_KRYTYCZNY) {
25      this._aktualnyPoziomZagrożenia++;
26      console.log(`UWAGA: Podniesiono globalny poziom zagrożenia do ${this._aktualnyPoziomZagrożenia}`);
27
28      // Wywołanie innej metody statycznej
29      this.powiadomOpoziomieZagrożenia();
30    } else {
31      console.log("UWAGA: Osiągnięto już maksymalny poziom zagrożenia!");
32    }
33  }
34
35  // Inna metoda statyczna
36  static obniżGlobalnyPoziomZagrożenia(): void {
37    if (this._aktualnyPoziomZagrożenia > this.POZIOM_MINIMALNY) {
38      this._aktualnyPoziomZagrożenia--;
39      console.log(`Obniżono globalny poziom zagrożenia do ${this._aktualnyPoziomZagrożenia}`);
40    } else {
41      console.log("Poziom zagrożenia jest już minimalny.");
42    }
43  }
44
45  // Statyczny getter
46  static get aktualnyPoziomZagrożenia(): number {
47    return this._aktualnyPoziomZagrożenia;
48  }
49
50  // Prywatna metoda statyczna
51  private static powiadomOpoziomieZagrożenia(): void {
52    console.log(`Powiadomienie: Aktualny poziom zagrożenia parku: ${this._aktualnyPoziomZagrożenia}`);
53  }
54
55  // Metoda instancji korzystająca z właściwości statycznych
56  aktualizujLokalnyPoziomZagrożenia(): void {
57    // Aktualizacja lokalnego poziomu na podstawie globalnego
58    this.lokalnyPoziomZagrożenia = ProtokółBezpieczeństwa._aktualnyPoziomZagrożenia;
59    console.log(`Obszar ${this.obszar}: zaktualizowano poziom zagrożenia do ${this.lokalnyPoziomZagrożenia}`);
60  }
61
62  get statusBezpieczeństwa(): string {
63    const poziomy = [
64      "NIEZNANY",
65      "MINIMALNY",
66      "NISKI",
67      "ŚREDNI",
68      "WYSOKI",
69      "KRYTYCZNY"
70    ];
71
72    return `Obszar: ${this.obszar}, Poziom zagrożenia: ${poziomy[this.lokalnyPoziomZagrożenia]}`;
73  }
74}
75
76// Użycie właściwości i metod statycznych - bez tworzenia instancji!
77console.log("Domyślny poziom zagrożenia:", ProtokółBezpieczeństwa.aktualnyPoziomZagrożenia);
78
79// Wywołanie metody statycznej bezpośrednio na klasie
80ProtokółBezpieczeństwa.podnieśGlobalnyPoziomZagrożenia();
81ProtokółBezpieczeństwa.podnieśGlobalnyPoziomZagrożenia();
82
83// Tworzenie instancji klasy
84const protokółCentralny = new ProtokółBezpieczeństwa("Centrum Operacyjne");
85const protokółWybieg = new ProtokółBezpieczeństwa("Wybieg T-Rex");
86
87// Wyświetlenie statusu bezpieczeństwa
88console.log(protokółCentralny.statusBezpieczeństwa);
89console.log(protokółWybieg.statusBezpieczeństwa);
90
91// Ponowne podniesienie globalnego poziomu zagrożenia
92ProtokółBezpieczeństwa.podnieśGlobalnyPoziomZagrożenia();
93
94// Aktualizacja lokalnych poziomów zagrożenia
95protokółCentralny.aktualizujLokalnyPoziomZagrożenia();
96protokółWybieg.aktualizujLokalnyPoziomZagrożenia();
97
98// Sprawdzenie zaktualizowanych statusów
99console.log(protokółCentralny.statusBezpieczeństwa);
100console.log(protokółWybieg.statusBezpieczeństwa);

W powyższym przykładzie:

  1. Stałe poziomów zagrożenia są właściwościami statycznymi, dostępnymi bezpośrednio przez klasę
  2. Statyczna właściwość
    _aktualnyPoziomZagrożenia
    jest współdzielona przez wszystkie instancje
  3. Metody statyczne mogą modyfikować statyczne właściwości i wywoływać inne metody statyczne
  4. Instancje klasy mogą aktualizować swoje wewnętrzne stany na podstawie statycznych właściwości

Kiedy używać elementów statycznych

Elementy statyczne są szczególnie przydatne w następujących przypadkach:

  1. Stałe i konfiguracja - Wartości, które pozostają niezmienne dla wszystkich instancji klasy
  2. Stan globalny - Kiedy potrzebujemy wartości współdzielonej przez wszystkie instancje
  3. Metody narzędziowe - Funkcje pomocnicze, które nie wymagają stanu instancji
  4. Metody fabryczne - Alternatywne sposoby tworzenia instancji klasy

Oto bardziej rozbudowany przykład pokazujący różne zastosowania elementów statycznych:

1class GenetycznyRejestr {
2  // Stałe statyczne
3  static readonly BAZA_NUKLEOTYDÓW = ["A", "C", "G", "T"];
4  static readonly MIN_DŁUGOŚĆ_DNA = 1000000;
5
6  // Stan globalny
7  private static _liczbaZarejestrowanychGatunków: number = 0;
8  private static _rejestracjeGatunków: Map<string, string> = new Map();
9
10  // Właściwości instancji
11  private id: string;
12  private sekwencjaDNA: string;
13  private nazwa: string;
14
15  constructor(nazwa: string, sekwencjaDNA: string) {
16    if (sekwencjaDNA.length < GenetycznyRejestr.MIN_DŁUGOŚĆ_DNA) {
17      throw new Error(`Sekwencja DNA jest za krótka. Wymagane minimum: ${GenetycznyRejestr.MIN_DŁUGOŚĆ_DNA} par zasad.`);
18    }
19
20    this.nazwa = nazwa;
21    this.sekwencjaDNA = sekwencjaDNA;
22    this.id = GenetycznyRejestr.wygenerujId(nazwa);
23
24    // Rejestracja nowego gatunku
25    GenetycznyRejestr._rejestracjeGatunków.set(this.id, nazwa);
26    GenetycznyRejestr._liczbaZarejestrowanychGatunków++;
27  }
28
29  // Metoda narzędziowa - tworzy ID na podstawie nazwy
30  private static wygenerujId(nazwa: string): string {
31    const timestamp = Date.now();
32    const cleanName = nazwa.toLowerCase().replace(/[^a-z0-9]/g, "");
33    return `DNO-${cleanName}-${timestamp}`;
34  }
35
36  // Metoda fabryczna - tworzy instancję z losowym DNA
37  static utwórzTestowyGatunek(nazwa: string): GenetycznyRejestr {
38    const długośćDNA = this.MIN_DŁUGOŚĆ_DNA + Math.floor(Math.random() * 500000);
39    let sekwencja = "";
40
41    for (let i = 0; i < długośćDNA; i++) {
42      const randomIndex = Math.floor(Math.random() * this.BAZA_NUKLEOTYDÓW.length);
43      sekwencja += this.BAZA_NUKLEOTYDÓW[randomIndex];
44    }
45
46    return new GenetycznyRejestr(nazwa, sekwencja);
47  }
48
49  // Metoda narzędziowa - weryfikuje poprawność sekwencji DNA
50  static weryfikujSekwencję(sekwencja: string): boolean {
51    if (sekwencja.length < this.MIN_DŁUGOŚĆ_DNA) return false;
52
53    // Sprawdza, czy sekwencja zawiera tylko dozwolone nukleotydy
54    for (const nukleotyd of sekwencja) {
55      if (!this.BAZA_NUKLEOTYDÓW.includes(nukleotyd)) {
56        return false;
57      }
58    }
59
60    return true;
61  }
62
63  // Getter dla statycznej właściwości
64  static get liczbaZarejestrowanychGatunków(): number {
65    return this._liczbaZarejestrowanychGatunków;
66  }
67
68  // Statyczna metoda zwracająca wszystkie zarejestrowane gatunki
69  static pobierzWszystkieGatunki(): string[] {
70    return Array.from(this._rejestracjeGatunków.values());
71  }
72
73  // Instancyjna metoda używająca statycznych pól
74  analizujSekwencję(): { proporcje: Record<string, number>, długość: number } {
75    const proporcje: Record<string, number> = {};
76
77    // Inicjalizacja liczników dla każdego nukleotydu
78    GenetycznyRejestr.BAZA_NUKLEOTYDÓW.forEach(n => {
79      proporcje[n] = 0;
80    });
81
82    // Zliczanie nukleotydów
83    for (const nukleotyd of this.sekwencjaDNA) {
84      proporcje[nukleotyd]++;
85    }
86
87    // Konwersja na procenty
88    GenetycznyRejestr.BAZA_NUKLEOTYDÓW.forEach(n => {
89      proporcje[n] = Number((proporcje[n] / this.sekwencjaDNA.length * 100).toFixed(2));
90    });
91
92    return {
93      proporcje,
94      długość: this.sekwencjaDNA.length
95    };
96  }
97
98  // Gettery instancji
99  get identyfikator(): string {
100    return this.id;
101  }
102
103  get gatunekNazwa(): string {
104    return this.nazwa;
105  }
106}
107
108// Korzystanie z metod i właściwości statycznych
109console.log("Minimalna długość DNA:", GenetycznyRejestr.MIN_DŁUGOŚĆ_DNA);
110console.log("Dostępne nukleotydy:", GenetycznyRejestr.BAZA_NUKLEOTYDÓW.join(", "));
111
112// Weryfikacja przykładowej sekwencji
113const przykładowaSekwencja = "ACGTACGTACGT".repeat(100000); // Powielenie, aby osiągnąć min. długość
114console.log("Weryfikacja sekwencji:", GenetycznyRejestr.weryfikujSekwencję(przykładowaSekwencja));
115
116// Tworzenie instancji za pomocą metody fabrycznej
117const trex = GenetycznyRejestr.utwórzTestowyGatunek("Tyrannosaurus Rex");
118const raptor = GenetycznyRejestr.utwórzTestowyGatunek("Velociraptor");
119const trike = GenetycznyRejestr.utwórzTestowyGatunek("Triceratops");
120
121// Sprawdzenie, ile gatunków zostało zarejestrowanych
122console.log("Liczba zarejestrowanych gatunków:", GenetycznyRejestr.liczbaZarejestrowanychGatunków);
123console.log("Lista gatunków:", GenetycznyRejestr.pobierzWszystkieGatunki());
124
125// Użycie metod instancji
126const analizaTrex = trex.analizujSekwencję();
127console.log(`Analiza ${trex.gatunekNazwa} (ID: ${trex.identyfikator}):`);
128console.log(`Długość sekwencji: ${analizaTrex.długość} par zasad`);
129console.log("Proporcje nukleotydów:", analizaTrex.proporcje);

Ten przykład pokazuje:

  1. Użycie stałych statycznych (
    BAZA_NUKLEOTYDÓW
    ,
    MIN_DŁUGOŚĆ_DNA
    )
  2. Globalny stan śledzący liczbę i rejestrację gatunków
  3. Statyczne metody narzędziowe jak
    weryfikujSekwencję
    i
    wygenerujId
  4. Metodę fabryczną
    utwórzTestowyGatunek
    tworzącą instancje z losowymi danymi
  5. Jak metody instancji mogą korzystać z właściwości statycznych

Klasy i metody abstrakcyjne

Klasy abstrakcyjne w TypeScript definiują szablon, który musi być zaimplementowany przez klasy dziedziczące. Nie można tworzyć instancji klasy abstrakcyjnej bezpośrednio - służy ona jako bazowy "wzorzec" dla innych klas.

Metody abstrakcyjne, określone w klasie abstrakcyjnej, muszą być zaimplementowane przez klasy dziedziczące. Definiują one kontrakt, określający, jakie metody muszą być dostępne, ale nie zawierają własnej implementacji.

Podstawy klas abstrakcyjnych

Zobaczmy, jak wykorzystać klasy abstrakcyjne w kontekście Parku Jurajskiego:

1// Abstrakcyjna klasa bazowa dla wszystkich dinozaurów
2abstract class Dinozaur {
3  // Właściwości wspólne dla wszystkich dinozaurów
4  private _id: string;
5  protected _nazwa: string;
6  protected _gatunek: string;
7  protected _wiek: number;
8  protected _waga: number;
9
10  constructor(id: string, nazwa: string, gatunek: string, wiek: number, waga: number) {
11    this._id = id;
12    this._nazwa = nazwa;
13    this._gatunek = gatunek;
14    this._wiek = wiek;
15    this._waga = waga;
16  }
17
18  // Gettery
19  get id(): string { return this._id; }
20  get nazwa(): string { return this._nazwa; }
21  get gatunek(): string { return this._gatunek; }
22  get wiek(): number { return this._wiek; }
23  get waga(): number { return this._waga; }
24
25  // Metody implementowane we wszystkich klasach pochodnych
26  abstract obliczZapotrzebowanieKaloryczne(): number;
27  abstract określPoziomZagrożenia(): number;
28
29  // Abstrakcyjna właściwość - musi być zaimplementowana w klasach pochodnych
30  abstract get typDiety(): string;
31
32  // Konkretna metoda (nie abstrakcyjna) - dostępna we wszystkich klasach pochodnych
33  pobierzPodstawoweDane(): string {
34    return `${this._nazwa} (${this._gatunek}) - ID: ${this._id}, Wiek: ${this._wiek} lat, Waga: ${this._waga} kg`;
35  }
36
37  // Metoda korzystająca z metod abstrakcyjnych
38  wygenerujRaportDinozaura(): string {
39    const kalorie = this.obliczZapotrzebowanieKaloryczne();
40    const poziomZagrożenia = this.określPoziomZagrożenia();
41
42    return `
43=== RAPORT DINOZAURA ===
44${this.pobierzPodstawoweDane()}
45Typ diety: ${this.typDiety}
46Dzienne zapotrzebowanie kaloryczne: ${kalorie} kcal
47Poziom zagrożenia: ${poziomZagrożenia}/10
48======================
49    `;
50  }
51}
52
53// Klasa dziedzicząca po klasie abstrakcyjnej
54class DrapieżnyDinozaur extends Dinozaur {
55  private _siłaUgryzienia: number;
56  private _prędkośćBiegu: number;
57
58  constructor(
59    id: string,
60    nazwa: string,
61    gatunek: string,
62    wiek: number,
63    waga: number,
64    siłaUgryzienia: number,
65    prędkośćBiegu: number
66  ) {
67    super(id, nazwa, gatunek, wiek, waga);
68    this._siłaUgryzienia = siłaUgryzienia;
69    this._prędkośćBiegu = prędkośćBiegu;
70  }
71
72  // Gettery
73  get siłaUgryzienia(): number { return this._siłaUgryzienia; }
74  get prędkośćBiegu(): number { return this._prędkośćBiegu; }
75
76  // Implementacja abstrakcyjnej właściwości
77  get typDiety(): string {
78    return "mięsożerny";
79  }
80
81  // Implementacje metod abstrakcyjnych
82  obliczZapotrzebowanieKaloryczne(): number {
83    // Dla drapieżników używamy złożonego wzoru
84    return Math.round(this._waga * 0.6 * (this._prędkośćBiegu * 0.2));
85  }
86
87  określPoziomZagrożenia(): number {
88    // Poziom zagrożenia zależy od siły ugryzienia, prędkości i wagi
89    const bazowy = (this._siłaUgryzienia * 0.7 + this._prędkośćBiegu * 0.3) / 100;
90    return Math.min(10, Math.max(1, Math.round(bazowy)));
91  }
92
93  // Metoda specyficzna dla drapieżników
94  poluj(): string {
95    return `${this._nazwa} poluje z prędkością ${this._prędkośćBiegu} km/h i siłą ugryzienia ${this._siłaUgryzienia} PSI`;
96  }
97}
98
99// Inna klasa dziedzicząca
100class RoślinożernyDinozaur extends Dinozaur {
101  private _długośćSzyi: number;
102  private _typPancerza: "brak" | "lekki" | "średni" | "ciężki";
103
104  constructor(
105    id: string,
106    nazwa: string,
107    gatunek: string,
108    wiek: number,
109    waga: number,
110    długośćSzyi: number,
111    typPancerza: "brak" | "lekki" | "średni" | "ciężki"
112  ) {
113    super(id, nazwa, gatunek, wiek, waga);
114    this._długośćSzyi = długośćSzyi;
115    this._typPancerza = typPancerza;
116  }
117
118  // Gettery
119  get długośćSzyi(): number { return this._długośćSzyi; }
120  get typPancerza(): string { return this._typPancerza; }
121
122  // Implementacja abstrakcyjnej właściwości
123  get typDiety(): string {
124    return "roślinożerny";
125  }
126
127  // Implementacje metod abstrakcyjnych
128  obliczZapotrzebowanieKaloryczne(): number {
129    // Dla roślinożerców używamy prostszego wzoru - potrzebują więcej kalorii na kilogram
130    return Math.round(this._waga * 0.8);
131  }
132
133  określPoziomZagrożenia(): number {
134    // Poziom zagrożenia jest niższy dla roślinożerców, ale zależy od wagi i typu pancerza
135    let bazowy = this._waga / 1000;
136
137    // Modyfikator zależny od pancerza
138    const modyfikatoryPancerza = {
139      "brak": 0,
140      "lekki": 1,
141      "średni": 2,
142      "ciężki": 3
143    };
144
145    bazowy += modyfikatoryPancerza[this._typPancerza];
146
147    return Math.min(7, Math.max(1, Math.round(bazowy))); // Maks. 7 dla roślinożerców
148  }
149
150  // Metoda specyficzna dla roślinożerców
151  pasięSię(): string {
152    const wysokośćSięgania = this._długośćSzyi > 2 ? "wysokich" : "niskich";
153    return `${this._nazwa} pasie się, zjadając rośliny z ${wysokośćSięgania} partii drzew`;
154  }
155}
156
157// Nie możemy utworzyć instancji klasy abstrakcyjnej
158// const nieprawidłowy = new Dinozaur("ID", "Nazwa", "Gatunek", 1, 100); // Błąd!
159
160// Tworzenie instancji klas pochodnych
161const trex = new DrapieżnyDinozaur("T-REX-1", "Rex", "Tyrannosaurus", 8, 7000, 12800, 32);
162const stegozaur = new RoślinożernyDinozaur("STEG-1", "Spike", "Stegosaurus", 12, 5000, 1.2, "ciężki");
163const diplodok = new RoślinożernyDinozaur("DIPL-1", "Dippy", "Diplodocus", 15, 16000, 6.5, "brak");
164
165// Korzystanie z metod zaimplementowanych w klasach pochodnych
166console.log(trex.wygenerujRaportDinozaura());
167console.log(stegozaur.wygenerujRaportDinozaura());
168console.log(diplodok.wygenerujRaportDinozaura());
169
170// Korzystanie z metod specyficznych dla danej klasy
171console.log(trex.poluj());
172console.log(stegozaur.pasięSię());
173
174// Dla obu typów dinozaurów możemy korzystać z metod klasy bazowej
175console.log(trex.pobierzPodstawoweDane());
176console.log(diplodok.pobierzPodstawoweDane());

W powyższym przykładzie:

  1. Dinozaur
    to klasa abstrakcyjna zawierająca wspólne właściwości i metody
  2. Abstrakcyjne metody
    obliczZapotrzebowanieKaloryczne()
    i
    określPoziomZagrożenia()
    muszą być zaimplementowane przez klasy dziedziczące
  3. Abstrakcyjna właściwość
    typDiety
    musi być zdefiniowana w klasach pochodnych
  4. Klasy
    DrapieżnyDinozaur
    i
    RoślinożernyDinozaur
    implementują abstrakcyjne metody i właściwości
  5. Nie można utworzyć instancji klasy
    Dinozaur
    bezpośrednio

Zastosowanie abstrakcyjnych klas i metod

Klasy abstrakcyjne są szczególnie przydatne w następujących przypadkach:

  1. Definiowanie wspólnego interfejsu - Określenie spójnego zestawu metod dla grupy powiązanych klas
  2. Współdzielenie kodu - Zapewnienie wspólnej implementacji dla grup klas
  3. Wymuszanie kontraktu - Gwarancja, że wszystkie klasy pochodne zaimplementują wymagane metody
  4. Tworzenie hierarchii typów - Budowanie złożonych relacji między typami

Zobaczmy bardziej zaawansowany przykład, łączący abstrakcyjne klasy i metody z elementami statycznymi w systemie zarządzania protokołami bezpieczeństwa Parku Jurajskiego:

1// Typy pomocnicze
2type PoziomAlertów = "normalny" | "podwyższony" | "wysoki" | "krytyczny";
3type StatusSystem = "aktywny" | "nieaktywny" | "w_konserwacji" | "awaria";
4
5// Interfejs dla systemów monitorujących
6interface SystemMonitorujący {
7  rozpocznijMonitorowanie(): void;
8  zakończMonitorowanie(): void;
9  pobierzAktualnyStatus(): StatusSystem;
10}
11
12// Abstrakcyjna klasa bazowa dla wszystkich systemów bezpieczeństwa
13abstract class SystemBezpieczeństwa implements SystemMonitorujący {
14  // Statyczne właściwości - dla wszystkich systemów bezpieczeństwa
15  private static _poziomAlertówParku: PoziomAlertów = "normalny";
16  private static _aktywneSystemy: Set<string> = new Set();
17  private static _incydenty: Array<{data: Date, typSystemu: string, lokalizacja: string, opis: string}> = [];
18
19  // Zwykłe właściwości
20  protected _id: string;
21  protected _lokalizacja: string;
22  protected _status: StatusSystem;
23  protected _czyMonitorowany: boolean;
24  protected _dataPrzeglądu: Date;
25
26  constructor(id: string, lokalizacja: string) {
27    this._id = id;
28    this._lokalizacja = lokalizacja;
29    this._status = "nieaktywny";
30    this._czyMonitorowany = false;
31    this._dataPrzeglądu = new Date();
32
33    // Rejestracja systemu
34    SystemBezpieczeństwa.zarejestrujSystem(id, this.constructor.name);
35  }
36
37  // Statyczne metody
38  static zarejestrujSystem(id: string, typSystemu: string): void {
39    console.log(`Zarejestrowano nowy system bezpieczeństwa: ${typSystemu} (ID: ${id})`);
40  }
41
42  static zgłośIncydent(typSystemu: string, lokalizacja: string, opis: string): void {
43    const incydent = {
44      data: new Date(),
45      typSystemu,
46      lokalizacja,
47      opis
48    };
49
50    this._incydenty.push(incydent);
51    console.log(`ALERT: Zgłoszono incydent w lokalizacji ${lokalizacja}: ${opis}`);
52
53    // Automatycznie podnosimy poziom alertów
54    if (this._poziomAlertówParku === "normalny") {
55      this.zmieńPoziomAlertów("podwyższony");
56    } else if (this._poziomAlertówParku === "podwyższony") {
57      this.zmieńPoziomAlertów("wysoki");
58    } else if (this._poziomAlertówParku === "wysoki") {
59      this.zmieńPoziomAlertów("krytyczny");
60    }
61  }
62
63  static zmieńPoziomAlertów(nowyPoziom: PoziomAlertów): void {
64    this._poziomAlertówParku = nowyPoziom;
65    console.log(`UWAGA: Zmieniono poziom alertów parku na: ${nowyPoziom.toUpperCase()}`);
66  }
67
68  static get poziomAlertów(): PoziomAlertów {
69    return this._poziomAlertówParku;
70  }
71
72  static get ostatnieIncydenty(): Array<{data: Date, typSystemu: string, lokalizacja: string, opis: string}> {
73    // Zwracamy 5 ostatnich incydentów
74    return [...this._incydenty].slice(-5);
75  }
76
77  // Gettery instancji
78  get id(): string { return this._id; }
79  get lokalizacja(): string { return this._lokalizacja; }
80  get status(): StatusSystem { return this._status; }
81
82  // Implementacja interfejsu SystemMonitorujący
83  rozpocznijMonitorowanie(): void {
84    this._czyMonitorowany = true;
85    SystemBezpieczeństwa._aktywneSystemy.add(this._id);
86    console.log(`Rozpoczęto monitorowanie systemu ${this.constructor.name} w lokalizacji ${this._lokalizacja}`);
87  }
88
89  zakończMonitorowanie(): void {
90    this._czyMonitorowany = false;
91    SystemBezpieczeństwa._aktywneSystemy.delete(this._id);
92    console.log(`Zakończono monitorowanie systemu ${this.constructor.name} w lokalizacji ${this._lokalizacja}`);
93  }
94
95  pobierzAktualnyStatus(): StatusSystem {
96    return this._status;
97  }
98
99  // Abstrakcyjne metody, które muszą być zaimplementowane
100  abstract uruchom(): void;
101  abstract zatrzymaj(): void;
102  abstract wykonajPrzegląd(): void;
103
104  // Abstrakcyjne właściwości
105  abstract get typSystemu(): string;
106  abstract get poziomPriorytetuAlarmowego(): number; // 1-10
107
108  // Metoda wykorzystująca abstrakcyjne właściwości i metody
109  wygenerujRaportStatusu(): string {
110    return `
111=== RAPORT SYSTEMU BEZPIECZEŃSTWA ===
112ID: ${this._id}
113Typ: ${this.typSystemu}
114Lokalizacja: ${this._lokalizacja}
115Status: ${this._status}
116Monitorowany: ${this._czyMonitorowany ? "TAK" : "NIE"}
117Priorytet alarmowy: ${this.poziomPriorytetuAlarmowego}/10
118Ostatni przegląd: ${this._dataPrzeglądu.toLocaleDateString()}
119Globalny poziom alertów: ${SystemBezpieczeństwa._poziomAlertówParku.toUpperCase()}
120=================================
121    `;
122  }
123}
124
125// Klasa pochodna - System Ogrodzenia Elektrycznego
126class SystemOgrodzeniaElektrycznego extends SystemBezpieczeństwa {
127  private _napięcie: number; // w woltach
128  private _długośćOgrodzenia: number; // w metrach
129  private _typZabezpieczenia: "standardowe" | "wzmocnione" | "ultrawzmocnione";
130
131  constructor(
132    id: string,
133    lokalizacja: string,
134    długośćOgrodzenia: number,
135    typZabezpieczenia: "standardowe" | "wzmocnione" | "ultrawzmocnione"
136  ) {
137    super(id, lokalizacja);
138    this._napięcie = 0;
139    this._długośćOgrodzenia = długośćOgrodzenia;
140    this._typZabezpieczenia = typZabezpieczenia;
141  }
142
143  // Implementacja abstrakcyjnych właściwości
144  get typSystemu(): string {
145    return "Ogrodzenie Elektryczne";
146  }
147
148  get poziomPriorytetuAlarmowego(): number {
149    // Priorytet zależy od typu zabezpieczenia
150    const bazowy = this._typZabezpieczenia === "standardowe" ? 7 :
151                  this._typZabezpieczenia === "wzmocnione" ? 8 : 10;
152
153    // Uwzględniamy długość ogrodzenia - dłuższe mają wyższy priorytet
154    const modyfikator = Math.min(2, Math.floor(this._długośćOgrodzenia / 1000));
155
156    return Math.min(10, bazowy + modyfikator);
157  }
158
159  // Implementacja abstrakcyjnych metod
160  uruchom(): void {
161    if (this._status === "w_konserwacji") {
162      console.log(`BŁĄD: Nie można uruchomić systemu ${this._id} - jest w trakcie konserwacji`);
163      return;
164    }
165
166    this._status = "aktywny";
167
168    // Napięcie zależy od typu zabezpieczenia
169    this._napięcie = this._typZabezpieczenia === "standardowe" ? 10000 :
170                   this._typZabezpieczenia === "wzmocnione" ? 20000 : 30000;
171
172    console.log(`Uruchomiono system ogrodzenia ${this._id} w lokalizacji ${this._lokalizacja}`);
173    console.log(`Napięcie: ${this._napięcie}V, Długość: ${this._długośćOgrodzenia}m`);
174  }
175
176  zatrzymaj(): void {
177    this._status = "nieaktywny";
178    this._napięcie = 0;
179    console.log(`Zatrzymano system ogrodzenia ${this._id} w lokalizacji ${this._lokalizacja}`);
180
181    // Zgłoszenie incydentu przy wysokim poziomie alertów
182    if (SystemBezpieczeństwa.poziomAlertów === "wysoki" || SystemBezpieczeństwa.poziomAlertów === "krytyczny") {
183      SystemBezpieczeństwa.zgłośIncydent(
184        this.typSystemu,
185        this._lokalizacja,
186        `Wyłączenie ogrodzenia przy wysokim poziomie alertów!`
187      );
188    }
189  }
190
191  wykonajPrzegląd(): void {
192    const poprzedniStatus = this._status;
193    this._status = "w_konserwacji";
194    console.log(`Rozpoczęto przegląd systemu ogrodzenia ${this._id}`);
195
196    // Symulacja przeglądu
197    setTimeout(() => {
198      this._dataPrzeglądu = new Date();
199      this._status = poprzedniStatus;
200      console.log(`Zakończono przegląd systemu ogrodzenia ${this._id}`);
201    }, 2000);
202  }
203
204  // Metody specyficzne dla tego typu systemu
205  zmieńNapięcie(noweNapięcie: number): void {
206    if (this._status !== "aktywny") {
207      console.log(`BŁĄD: Nie można zmienić napięcia - system nie jest aktywny`);
208      return;
209    }
210
211    this._napięcie = noweNapięcie;
212    console.log(`Zmieniono napięcie ogrodzenia ${this._id} na ${noweNapięcie}V`);
213  }
214
215  zgłośPrzerwęWOgrodzeniu(): void {
216    this._status = "awaria";
217    SystemBezpieczeństwa.zgłośIncydent(
218      this.typSystemu,
219      this._lokalizacja,
220      `Wykryto przerwę w ogrodzeniu elektrycznym!`
221    );
222  }
223}
224
225// Inna klasa pochodna - System Monitoringu Wideo
226class SystemMonitoringuWideo extends SystemBezpieczeństwa {
227  private _liczbaKamer: number;
228  private _rozdzielczość: "SD" | "HD" | "4K";
229  private _trybNocny: boolean;
230
231  constructor(
232    id: string,
233    lokalizacja: string,
234    liczbaKamer: number,
235    rozdzielczość: "SD" | "HD" | "4K"
236  ) {
237    super(id, lokalizacja);
238    this._liczbaKamer = liczbaKamer;
239    this._rozdzielczość = rozdzielczość;
240    this._trybNocny = false;
241  }
242
243  // Implementacja abstrakcyjnych właściwości
244  get typSystemu(): string {
245    return "Monitoring Wideo";
246  }
247
248  get poziomPriorytetuAlarmowego(): number {
249    // Priorytet zależy od liczby kamer i rozdzielczości
250    const bazowy = 5;
251
252    // Modyfikator rozdzielczości
253    const modyfikatorRozdzielczości =
254      this._rozdzielczość === "SD" ? 0 :
255      this._rozdzielczość === "HD" ? 1 : 2;
256
257    // Modyfikator liczby kamer
258    const modyfikatorKamer = Math.min(2, Math.floor(this._liczbaKamer / 10));
259
260    return Math.min(10, bazowy + modyfikatorRozdzielczości + modyfikatorKamer);
261  }
262
263  // Implementacja abstrakcyjnych metod
264  uruchom(): void {
265    this._status = "aktywny";
266    console.log(`Uruchomiono system monitoringu ${this._id} w lokalizacji ${this._lokalizacja}`);
267    console.log(`Liczba kamer: ${this._liczbaKamer}, Rozdzielczość: ${this._rozdzielczość}`);
268  }
269
270  zatrzymaj(): void {
271    this._status = "nieaktywny";
272    console.log(`Zatrzymano system monitoringu ${this._id} w lokalizacji ${this._lokalizacja}`);
273  }
274
275  wykonajPrzegląd(): void {
276    this._status = "w_konserwacji";
277    console.log(`Rozpoczęto przegląd systemu monitoringu ${this._id}`);
278
279    // W tym przypadku przegląd kończy się natychmiast
280    this._dataPrzeglądu = new Date();
281    this._status = "aktywny";
282    console.log(`Zakończono przegląd systemu monitoringu ${this._id}`);
283  }
284
285  // Metody specyficzne dla tego typu systemu
286  włączTrybNocny(): void {
287    this._trybNocny = true;
288    console.log(`Włączono tryb nocny w systemie monitoringu ${this._id}`);
289  }
290
291  wyłączTrybNocny(): void {
292    this._trybNocny = false;
293    console.log(`Wyłączono tryb nocny w systemie monitoringu ${this._id}`);
294  }
295
296  wykryjRuch(obszar: string): void {
297    console.log(`System ${this._id}: Wykryto ruch w obszarze ${obszar}`);
298
299    if (SystemBezpieczeństwa.poziomAlertów !== "normalny") {
300      SystemBezpieczeństwa.zgłośIncydent(
301        this.typSystemu,
302        `${this._lokalizacja} - obszar ${obszar}`,
303        `Wykryto nieautoryzowany ruch!`
304      );
305    }
306  }
307}
308
309// Demonstracja systemu bezpieczeństwa Parku Jurajskiego
310function testSystemuBezpieczeństwa() {
311  console.log("=== INICJALIZACJA SYSTEMÓW BEZPIECZEŃSTWA PARKU JURAJSKIEGO ===");
312
313  // Tworzenie systemów ogrodzenia
314  const ogrodzenieGłówne = new SystemOgrodzeniaElektrycznego("FNC-001", "Sektor A - Wejście główne", 2500, "ultrawzmocnione");
315  const ogrodzenieTRex = new SystemOgrodzeniaElektrycznego("FNC-002", "Sektor B - Wybieg T-Rex", 1200, "ultrawzmocnione");
316  const ogrodzenieRaptory = new SystemOgrodzeniaElektrycznego("FNC-003", "Sektor C - Wybieg Raptorów", 800, "wzmocnione");
317
318  // Tworzenie systemów monitoringu
319  const monitoringGłówny = new SystemMonitoringuWideo("CAM-001", "Centrum kontroli", 20, "4K");
320  const monitoringTRex = new SystemMonitoringuWideo("CAM-002", "Sektor B - Wybieg T-Rex", 12, "HD");
321  const monitoringRaptory = new SystemMonitoringuWideo("CAM-003", "Sektor C - Wybieg Raptorów", 8, "HD");
322
323  // Uruchamianie wszystkich systemów
324  console.log("\n=== URUCHAMIANIE SYSTEMÓW ===");
325  ogrodzenieGłówne.uruchom();
326  ogrodzenieTRex.uruchom();
327  ogrodzenieRaptory.uruchom();
328
329  monitoringGłówny.uruchom();
330  monitoringTRex.uruchom();
331  monitoringRaptory.uruchom();
332
333  // Rozpoczęcie monitorowania
334  console.log("\n=== AKTYWACJA MONITOROWANIA ===");
335  ogrodzenieGłówne.rozpocznijMonitorowanie();
336  ogrodzenieTRex.rozpocznijMonitorowanie();
337  ogrodzenieRaptory.rozpocznijMonitorowanie();
338
339  monitoringGłówny.rozpocznijMonitorowanie();
340  monitoringTRex.rozpocznijMonitorowanie();
341  monitoringRaptory.rozpocznijMonitorowanie();
342
343  // Sprawdzenie statusów
344  console.log("\n=== RAPORTY SYSTEMÓW ===");
345  console.log(ogrodzenieGłówne.wygenerujRaportStatusu());
346  console.log(monitoringTRex.wygenerujRaportStatusu());
347
348  // Symulacja incydentu
349  console.log("\n=== SYMULACJA INCYDENTU ===");
350  console.log("Aktualny poziom alertów:", SystemBezpieczeństwa.poziomAlertów);
351
352  monitoringRaptory.wykryjRuch("Południowy narożnik");
353  ogrodzenieRaptory.zgłośPrzerwęWOgrodzeniu();
354
355  // Sprawdzenie poziom alertów po incydentach
356  console.log("\nAktualny poziom alertów po incydentach:", SystemBezpieczeństwa.poziomAlertów);
357
358  // Wyświetlenie ostatnich incydentów
359  console.log("\n=== OSTATNIE INCYDENTY ===");
360  SystemBezpieczeństwa.ostatnieIncydenty.forEach((incydent, index) => {
361    console.log(`${index + 1}. [${incydent.data.toLocaleString()}] ${incydent.typSystemu} w ${incydent.lokalizacja}: ${incydent.opis}`);
362  });
363
364  // Przeprowadzenie przeglądu
365  console.log("\n=== PRZEGLĄD SYSTEMÓW ===");
366  ogrodzenieTRex.wykonajPrzegląd();
367  monitoringGłówny.wykonajPrzegląd();
368
369  // Ręczna zmiana poziomu alertów
370  console.log("\n=== NORMALIZACJA SYTUACJI ===");
371  SystemBezpieczeństwa.zmieńPoziomAlertów("normalny");
372}
373
374// Uruchomienie testu
375testSystemuBezpieczeństwa();

W tym zaawansowanym przykładzie:

  1. Używamy abstrakcyjnej klasy
    SystemBezpieczeństwa
    jako bazę dla wszystkich systemów bezpieczeństwa
  2. Klasa definiuje statyczne właściwości i metody (np. poziom alertów parku, lista incydentów)
  3. Abstrakcyjne metody i właściwości wymuszają implementację specyficznej funkcjonalności w klasach pochodnych
  4. Klasy pochodne (
    SystemOgrodzeniaElektrycznego
    i
    SystemMonitoringuWideo
    ) implementują abstrakcyjne elementy
  5. Elementy statyczne pozwalają na centralną koordynację (np. zgłaszanie incydentów i zarządzanie poziomem alertów)

Łączenie elementów statycznych i abstrakcyjnych

Elementy statyczne i abstrakcyjne mogą być potężnym narzędziem, gdy są używane razem w dobrze zaprojektowanej hierarchii klas. Oto kilka wzorców i przykładów ich zastosowania:

1. Abstrakcyjna klasa bazowa ze statycznymi metodami fabrycznymi

1abstract class GatunekDinozaura {
2  // Właściwości statyczne
3  private static _zarejestrowaneDinozaury: Map<string, GatunekDinozaura> = new Map();
4
5  // Właściwości instancji
6  private _nazwa: string;
7  private _eraDominocenitryczna: string;
8  protected _dietaTyp: "mięsożerny" | "roślinożerny" | "wszystkożerny";
9
10  constructor(nazwa: string, era: string, dieta: "mięsożerny" | "roślinożerny" | "wszystkożerny") {
11    this._nazwa = nazwa;
12    this._eraDominocenitryczna = era;
13    this._dietaTyp = dieta;
14
15    // Dodanie do rejestru
16    GatunekDinozaura._zarejestrowaneDinozaury.set(nazwa, this);
17  }
18
19  // Statyczna metoda fabryczna - tworzy odpowiedni gatunek na podstawie diety
20  static stwórzGatunek(
21    nazwa: string,
22    era: string,
23    dieta: "mięsożerny" | "roślinożerny" | "wszystkożerny",
24    parametryDodatkowe: Record<string, any>
25  ): GatunekDinozaura {
26    if (dieta === "mięsożerny") {
27      return new MięsożernyGatunek(
28        nazwa,
29        era,
30        parametryDodatkowe.siłaUgryzienia,
31        parametryDodatkowe.prędkośćBiegu
32      );
33    } else {
34      return new RoślinożernyGatunek(
35        nazwa,
36        era,
37        parametryDodatkowe.długośćSzyi || 0,
38        parametryDodatkowe.posiadaPancerz || false
39      );
40    }
41  }
42
43  // Statyczna metoda do pobierania zarejestrowanych gatunków
44  static pobierzGatunek(nazwa: string): GatunekDinozaura | undefined {
45    return this._zarejestrowaneDinozaury.get(nazwa);
46  }
47
48  static get wszystkieGatunki(): string[] {
49    return Array.from(this._zarejestrowaneDinozaury.keys());
50  }
51
52  // Gettery instancji
53  get nazwa(): string { return this._nazwa; }
54  get era(): string { return this._eraDominocenitryczna; }
55  get typDiety(): string { return this._dietaTyp; }
56
57  // Abstrakcyjne metody
58  abstract opisZachowania(): string;
59  abstract obliczSzacowanąWagę(długość: number): number;
60
61  // Metoda wykorzystująca abstrakcyjne metody
62  generujOpis(): string {
63    return `${this._nazwa} - dinozaur ${this._dietaTyp} z ery ${this._eraDominocenitryczna}.
64${this.opisZachowania()}`;
65  }
66}
67
68// Klasy pochodne
69class MięsożernyGatunek extends GatunekDinozaura {
70  private _siłaUgryzienia: number; // w PSI
71  private _prędkośćBiegu: number; // w km/h
72
73  constructor(nazwa: string, era: string, siłaUgryzienia: number, prędkośćBiegu: number) {
74    super(nazwa, era, "mięsożerny");
75    this._siłaUgryzienia = siłaUgryzienia;
76    this._prędkośćBiegu = prędkośćBiegu;
77  }
78
79  // Gettery
80  get siłaUgryzienia(): number { return this._siłaUgryzienia; }
81  get prędkośćBiegu(): number { return this._prędkośćBiegu; }
82
83  // Implementacja abstrakcyjnych metod
84  opisZachowania(): string {
85    return `Poluje z prędkością ${this._prędkośćBiegu} km/h i siłą ugryzienia ${this._siłaUgryzienia} PSI.`;
86  }
87
88  obliczSzacowanąWagę(długość: number): number {
89    // Przybliżony wzór dla drapieżników
90    return długość * długość * 100;
91  }
92}
93
94class RoślinożernyGatunek extends GatunekDinozaura {
95  private _długośćSzyi: number; // w metrach
96  private _posiadaPancerz: boolean;
97
98  constructor(nazwa: string, era: string, długośćSzyi: number, posiadaPancerz: boolean) {
99    super(nazwa, era, "roślinożerny");
100    this._długośćSzyi = długośćSzyi;
101    this._posiadaPancerz = posiadaPancerz;
102  }
103
104  // Gettery
105  get długośćSzyi(): number { return this._długośćSzyi; }
106  get posiadaPancerz(): boolean { return this._posiadaPancerz; }
107
108  // Implementacja abstrakcyjnych metod
109  opisZachowania(): string {
110    const opisSzyi = this._długośćSzyi > 3
111      ? `Posiada długą szyję (${this._długośćSzyi}m) umożliwiającą zjadanie wysokiej roślinności.`
112      : "Żywi się niską roślinnością.";
113
114    const opisPancerza = this._posiadaPancerz
115      ? "Posiada pancerz chroniący przed drapieżnikami."
116      : "Nie posiada pancerza ochronnego.";
117
118    return `${opisSzyi} ${opisPancerza}`;
119  }
120
121  obliczSzacowanąWagę(długość: number): number {
122    // Przybliżony wzór dla roślinożerców (zwykle cięższe)
123    let waga = długość * długość * 150;
124
125    // Dodatkowa waga za pancerz
126    if (this._posiadaPancerz) {
127      waga *= 1.3;
128    }
129
130    return waga;
131  }
132}
133
134// Użycie
135function testGatunkówDinozaurów() {
136  // Tworzenie gatunków za pomocą metody fabrycznej
137  GatunekDinozaura.stwórzGatunek(
138    "Tyrannosaurus Rex",
139    "późna kreda",
140    "mięsożerny",
141    { siłaUgryzienia: 12800, prędkośćBiegu: 32 }
142  );
143
144  GatunekDinozaura.stwórzGatunek(
145    "Velociraptor",
146    "późna kreda",
147    "mięsożerny",
148    { siłaUgryzienia: 2000, prędkośćBiegu: 64 }
149  );
150
151  GatunekDinozaura.stwórzGatunek(
152    "Brachiosaurus",
153    "późna jura",
154    "roślinożerny",
155    { długośćSzyi: 9, posiadaPancerz: false }
156  );
157
158  GatunekDinozaura.stwórzGatunek(
159    "Stegosaurus",
160    "późna jura",
161    "roślinożerny",
162    { długośćSzyi: 0.5, posiadaPancerz: true }
163  );
164
165  // Dostęp do zarejestrowanych gatunków
166  console.log("Dostępne gatunki:", GatunekDinozaura.wszystkieGatunki);
167
168  // Pobieranie informacji o gatunkach
169  const trex = GatunekDinozaura.pobierzGatunek("Tyrannosaurus Rex");
170  const brach = GatunekDinozaura.pobierzGatunek("Brachiosaurus");
171
172  if (trex) {
173    console.log(trex.generujOpis());
174    console.log(`Szacowana waga (12m długości): ${trex.obliczSzacowanąWagę(12)} kg`);
175  }
176
177  if (brach) {
178    console.log(brach.generujOpis());
179    console.log(`Szacowana waga (25m długości): ${brach.obliczSzacowanąWagę(25)} kg`);
180  }
181
182  // Sprawdzanie typu
183  const steg = GatunekDinozaura.pobierzGatunek("Stegosaurus") as RoślinożernyGatunek;
184  if (steg && steg instanceof RoślinożernyGatunek) {
185    console.log(`Stegosaurus posiada pancerz: ${steg.posiadaPancerz ? "TAK" : "NIE"}`);
186  }
187
188  const raptor = GatunekDinozaura.pobierzGatunek("Velociraptor") as MięsożernyGatunek;
189  if (raptor && raptor instanceof MięsożernyGatunek) {
190    console.log(`Velociraptor - prędkość biegu: ${raptor.prędkośćBiegu} km/h`);
191  }
192}
193
194testGatunkówDinozaurów();

2. Abstrakcyjne metody statyczne

W TypeScript od wersji 4.0 możliwe jest definiowanie abstrakcyjnych metod statycznych. Umożliwia to tworzenie hierarchii klas, gdzie klasy pochodne muszą implementować określone metody statyczne:

1abstract class SystemLokacji {
2  // Właściwości instancji
3  protected _id: string;
4  protected _nazwa: string;
5
6  constructor(id: string, nazwa: string) {
7    this._id = id;
8    this._nazwa = nazwa;
9  }
10
11  // Abstrakcyjna statyczna metoda, którą muszą zaimplementować klasy pochodne
12  static abstract generujIdentyfikatorLokacji(typ: string, numer: number): string;
13
14  // Zwykła abstrakcyjna metoda instancji
15  abstract pobierzInformacjeOLokacji(): Record<string, any>;
16
17  // Metoda instancji
18  wygenerujRaport(): string {
19    const info = this.pobierzInformacjeOLokacji();
20
21    let raport = `=== RAPORT LOKACJI: ${this._nazwa} (ID: ${this._id}) ===\n`;
22
23    for (const [klucz, wartość] of Object.entries(info)) {
24      raport += `${klucz}: ${wartość}\n`;
25    }
26
27    return raport;
28  }
29}
30
31class WybiegDinozaurów extends SystemLokacji {
32  private _gatunek: string;
33  private _powierzchnia: number; // w hektarach
34  private _liczbaOsobników: number;
35
36  constructor(numer: number, nazwa: string, gatunek: string, powierzchnia: number, liczbaOsobników: number) {
37    // Generowanie ID dla wybiegu
38    const id = WybiegDinozaurów.generujIdentyfikatorLokacji("wybieg", numer);
39    super(id, nazwa);
40
41    this._gatunek = gatunek;
42    this._powierzchnia = powierzchnia;
43    this._liczbaOsobników = liczbaOsobników;
44  }
45
46  // Implementacja abstrakcyjnej statycznej metody
47  static generujIdentyfikatorLokacji(typ: string, numer: number): string {
48    return `${typ.toUpperCase()}-${numer.toString().padStart(3, '0')}`;
49  }
50
51  // Implementacja abstrakcyjnej metody instancji
52  pobierzInformacjeOLokacji(): Record<string, any> {
53    return {
54      typ: "Wybieg dinozaurów",
55      gatunek: this._gatunek,
56      powierzchnia: `${this._powierzchnia} ha`,
57      liczbaOsobników: this._liczbaOsobników,
58      gęstośćZaludnienia: `${(this._liczbaOsobników / this._powierzchnia).toFixed(2)} os./ha`
59    };
60  }
61
62  // Metody specyficzne dla tej klasy
63  dodajOsobnika(): void {
64    this._liczbaOsobników++;
65    console.log(`Dodano nowego osobnika do wybiegu ${this._nazwa}. Nowa liczba: ${this._liczbaOsobników}`);
66  }
67
68  usuńOsobnika(): boolean {
69    if (this._liczbaOsobników > 0) {
70      this._liczbaOsobników--;
71      console.log(`Usunięto osobnika z wybiegu ${this._nazwa}. Nowa liczba: ${this._liczbaOsobników}`);
72      return true;
73    }
74
75    console.log(`Błąd: Nie można usunąć osobnika z wybiegu ${this._nazwa} - brak osobników.`);
76    return false;
77  }
78}
79
80class AtrakcjaTurystyczna extends SystemLokacji {
81  private _typAtrakcji: string;
82  private _pojemność: number;
83  private _czasOczekiwania: number; // w minutach
84
85  constructor(numer: number, nazwa: string, typAtrakcji: string, pojemność: number) {
86    // Generowanie ID dla atrakcji
87    const id = AtrakcjaTurystyczna.generujIdentyfikatorLokacji("atrakcja", numer);
88    super(id, nazwa);
89
90    this._typAtrakcji = typAtrakcji;
91    this._pojemność = pojemność;
92    this._czasOczekiwania = 0;
93  }
94
95  // Implementacja abstrakcyjnej statycznej metody
96  static generujIdentyfikatorLokacji(typ: string, numer: number): string {
97    // Inny format ID dla atrakcji
98    return `${typ.substring(0, 3).toUpperCase()}${numer.toString().padStart(4, '0')}`;
99  }
100
101  // Implementacja abstrakcyjnej metody instancji
102  pobierzInformacjeOLokacji(): Record<string, any> {
103    return {
104      typ: "Atrakcja turystyczna",
105      kategoria: this._typAtrakcji,
106      pojemność: this._pojemność,
107      czasOczekiwania: `${this._czasOczekiwania} min`
108    };
109  }
110
111  // Metody specyficzne dla tej klasy
112  aktualizujCzasOczekiwania(nowyOczekiwanieCzas: number): void {
113    this._czasOczekiwania = nowyOczekiwanieCzas;
114    console.log(`Zaktualizowano czas oczekiwania dla ${this._nazwa}: ${this._czasOczekiwania} min`);
115  }
116}
117
118// Zastosowanie
119function testSystemLokacji() {
120  // Tworzenie wybiegów
121  const wybiegTRex = new WybiegDinozaurów(1, "Królestwo T-Rexa", "Tyrannosaurus Rex", 5, 1);
122  const wybiegRaptorów = new WybiegDinozaurów(2, "Dolina Raptorów", "Velociraptor", 3, 6);
123
124  // Tworzenie atrakcji
125  const centrumOdwiedzających = new AtrakcjaTurystyczna(101, "Centrum Genetyki", "edukacyjna", 500);
126  const safariJeepem = new AtrakcjaTurystyczna(102, "Safari Jurajskie", "przejażdżka", 50);
127
128  // Wyświetlanie raportów
129  console.log(wybiegTRex.wygenerujRaport());
130  console.log(wybiegRaptorów.wygenerujRaport());
131  console.log(centrumOdwiedzających.wygenerujRaport());
132  console.log(safariJeepem.wygenerujRaport());
133
134  // Wykonywanie operacji
135  wybiegRaptorów.dodajOsobnika();
136  wybiegRaptorów.dodajOsobnika();
137  safariJeepem.aktualizujCzasOczekiwania(45);
138
139  // Sprawdzenie zaktualizowanych danych
140  console.log(wybiegRaptorów.wygenerujRaport());
141  console.log(safariJeepem.wygenerujRaport());
142
143  // Demonstracja różnych formatów ID generowanych przez implementacje statycznych metod
144  console.log("Format ID wybiegu:", WybiegDinozaurów.generujIdentyfikatorLokacji("wybieg", 42));
145  console.log("Format ID atrakcji:", AtrakcjaTurystyczna.generujIdentyfikatorLokacji("atrakcja", 42));
146}
147
148testSystemLokacji();

Podsumowanie

W tym ćwiczeniu poznaliśmy dwa potężne mechanizmy TypeScript:

  1. Właściwości i metody statyczne:

    • Należą do klasy, a nie do instancji
    • Umożliwiają współdzielenie danych i funkcjonalności między wszystkimi instancjami
    • Idealne do definiowania stałych, globalnego stanu i metod narzędziowych
    • Dostępne bezpośrednio przez nazwę klasy, bez konieczności tworzenia instancji
  2. Klasy i metody abstrakcyjne:

    • Definiują "szablon" lub "kontrakt", który musi być zaimplementowany przez klasy dziedziczące
    • Nie można tworzyć instancji klas abstrakcyjnych bezpośrednio
    • Wymuszają implementację określonych metod w klasach pochodnych
    • Pozwalają na współdzielenie wspólnej funkcjonalności przez hierarchię klas

Łącząc te koncepcje, możemy tworzyć eleganckie, modułowe i typowo bezpieczne systemy, które idealnie nadają się do modelowania złożonych relacji i procesów, takich jak te w systemie zarządzania Parkiem Jurajskim.

Dr. Henry Wu podsumowałby to następująco: "W naturze, podobnie jak w programowaniu, istnieje piękno w strukturze i porządku. Elementy statyczne zapewniają spójność dla całego gatunku, podczas gdy abstrakcyjne koncepcje stanowią podstawowy plan, który każdy organizm musi zaimplementować na swój własny sposób. Razem tworzą doskonałą harmonię - z odpowiednimi zabezpieczeniami, oczywiście... w większości przypadków."

Przejdź do CodeWorlds