Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Funkcje generyczne

Witaj ponownie w najbardziej zaawansowanym laboratorium genetycznym Parku Jurajskiego! Dr. Henry Wu dziś zaprasza nas do zgłębienia jednego z najpotężniejszych mechanizmów TypeScript - funkcji generycznych. Podobnie jak zaawansowana inżynieria genetyczna pozwala nam tworzyć nowe gatunki dinozaurów z różnym DNA, funkcje generyczne pozwalają na tworzenie elastycznych, wielokrotnego użytku rozwiązań, które działają z różnymi typami danych, zachowując jednocześnie pełne bezpieczeństwo typów.

Czym są funkcje generyczne?

Funkcje generyczne to funkcje, które mogą pracować z różnymi typami danych, zachowując przy tym pełną kontrolę typów. Zamiast definiować konkretny typ parametru i zwracanej wartości, używamy parametrów typu (oznaczonych zwykle jako

T
,
U
,
K
itp.), które są "wypełniane" konkretnymi typami w momencie wywołania funkcji.

Wyobraź sobie, że zamiast tworzyć osobną funkcję dla każdego gatunku dinozaura, możesz stworzyć jedną generyczną funkcję, która będzie działała z każdym gatunkiem!

Podstawowa składnia funkcji generycznych

Przyjrzyjmy się, jak definiuje się i używa funkcji generycznych w TypeScript:

1// Prosta funkcja generyczna - zwraca ten sam element, który został przekazany
2function identycznośćGenetyczna<T>(obiekt: T): T {
3  return obiekt;
4}
5
6// Użycie z różnymi typami
7const łańcuchDNA: string = identycznośćGenetyczna<string>("ACGTACGT");
8const wiekTriceratopsa: number = identycznośćGenetyczna<number>(12);
9const aktywnyStatus: boolean = identycznośćGenetyczna<boolean>(true);
10
11// TypeScript często może wywnioskować typ generyczny na podstawie przekazanego argumentu
12const poziomAgresji = identycznośćGenetyczna(7); // TypeScript wnioskuje T jako number

W powyższym przykładzie:

  1. Definiujemy funkcję generyczną
    identycznośćGenetyczna
    z parametrem typu
    T
  2. Funkcja przyjmuje argument typu
    T
    i zwraca wartość tego samego typu
  3. Używamy funkcji z różnymi typami: string, number, boolean
  4. W ostatnim przykładzie TypeScript automatycznie wnioskuje typ generyczny na podstawie przekazanego argumentu

Funkcje generyczne z wieloma parametrami typu

Funkcje generyczne mogą mieć wiele parametrów typu, co pozwala na bardziej złożone operacje:

1// Funkcja generyczna z dwoma parametrami typu
2function parujDaneDinozaurów<T, U>(pierwsze: T, drugie: U): [T, U] {
3  return [pierwsze, drugie];
4}
5
6// Przykładowe użycie
7interface DanePodstawoweDinozaura {
8  nazwa: string;
9  gatunek: string;
10  wiek: number;
11}
12
13interface DaneZdrowotne {
14  temperaturaCiała: number;
15  tętno: number;
16  poziomEnergii: number;
17}
18
19const rexPodstawowe: DanePodstawoweDinozaura = {
20  nazwa: "Rex",
21  gatunek: "Tyrannosaurus",
22  wiek: 7
23};
24
25const rexZdrowie: DaneZdrowotne = {
26  temperaturaCiała: 38.2,
27  tętno: 76,
28  poziomEnergii: 8.5
29};
30
31// Łączenie różnych typów danych
32const pełneDaneRexa = parujDaneDinozaurów<DanePodstawoweDinozaura, DaneZdrowotne>(
33  rexPodstawowe,
34  rexZdrowie
35);
36
37console.log(`${pełneDaneRexa[0].nazwa} ma temperaturę ciała ${pełneDaneRexa[1].temperaturaCiała}°C`);

Ograniczenia generyczne (Generic Constraints)

Czasami chcemy, aby nasze funkcje generyczne działały tylko z określonymi typami danych, które spełniają pewne wymagania. Możemy to osiągnąć za pomocą ograniczeń generycznych poprzez słowo kluczowe

extends
:

1// Definiujemy interfejs opisujący minimalne wymagania
2interface PodstawoweInformacjeDinozaura {
3  id: string;
4  nazwa: string;
5  gatunek: string;
6}
7
8// Funkcja generyczna z ograniczeniem - T musi zawierać co najmniej właściwości z PodstawoweInformacjeDinozaura
9function wyświetlInformacjeDinozaura<T extends PodstawoweInformacjeDinozaura>(dino: T): void {
10  console.log(`ID: ${dino.id}`);
11  console.log(`Nazwa: ${dino.nazwa}`);
12  console.log(`Gatunek: ${dino.gatunek}`);
13
14  // Możemy również uzyskać dostęp do dodatkowych właściwości, które mogą być obecne w T
15  console.log("Dodatkowe informacje:");
16  const podstawoweKlucze = Object.keys({} as PodstawoweInformacjeDinozaura);
17  Object.entries(dino).forEach(([klucz, wartość]) => {
18    if (!podstawoweKlucze.includes(klucz)) {
19      console.log(`- ${klucz}: ${wartość}`);
20    }
21  });
22}
23
24// Przykład użycia z różnymi typami spełniającymi ograniczenie
25interface TRex extends PodstawoweInformacjeDinozaura {
26  siłaUgryzienia: number;
27  prędkośćBiegu: number;
28}
29
30const rex: TRex = {
31  id: "TREX-001",
32  nazwa: "Rexy",
33  gatunek: "Tyrannosaurus Rex",
34  siłaUgryzienia: 12800,
35  prędkośćBiegu: 32
36};
37
38interface Velociraptor extends PodstawoweInformacjeDinozaura {
39  inteligencja: number;
40  polowanieWGrupie: boolean;
41}
42
43const raptor: Velociraptor = {
44  id: "VELOC-001",
45  nazwa: "Blue",
46  gatunek: "Velociraptor",
47  inteligencja: 9,
48  polowanieWGrupie: true
49};
50
51// Obie funkcje zadziałają, ponieważ oba typy rozszerzają PodstawoweInformacjeDinozaura
52wyświetlInformacjeDinozaura(rex);
53wyświetlInformacjeDinozaura(raptor);
54
55// To nie zadziała, ponieważ brakuje wymaganych właściwości
56// wyświetlInformacjeDinozaura({ waga: 5000, wiek: 12 });

W powyższym przykładzie:

  1. Definiujemy interfejs
    PodstawoweInformacjeDinozaura
    z minimalnymi wymaganiami
  2. Tworzymy funkcję generyczną, która działa tylko z typami rozszerzającymi ten interfejs
  3. Funkcja może bezpiecznie korzystać z właściwości wymaganych przez interfejs
  4. Możemy użyć funkcji z różnymi typami dinozaurów, o ile spełniają podstawowe wymagania

Ograniczenia z kluczami (keyof constraint)

Słowo kluczowe

keyof
pozwala na ograniczenie parametru generycznego do kluczy innego typu. Jest to niezwykle przydatne przy tworzeniu funkcji, które operują na kluczach obiektów:

1// Funkcja pobierająca wartość właściwości na podstawie jej klucza
2function pobierzWłaściwośćDinozaura<T, K extends keyof T>(obiekt: T, klucz: K): T[K] {
3  return obiekt[klucz];
4}
5
6// Przykładowe dane dinozaura
7interface DaneDinozaura {
8  id: string;
9  nazwa: string;
10  gatunek: string;
11  wiek: number;
12  waga: number;
13  drapieżnik: boolean;
14  cechy: string[];
15}
16
17const triceratops: DaneDinozaura = {
18  id: "TRIC-001",
19  nazwa: "Tricky",
20  gatunek: "Triceratops",
21  wiek: 15,
22  waga: 9000,
23  drapieżnik: false,
24  cechy: ["rogi", "tarcza na głowie", "silne nogi"]
25};
26
27// Użycie funkcji z różnymi kluczami
28const nazwaTriceratopsa = pobierzWłaściwośćDinozaura(triceratops, "nazwa");
29console.log(`Nazwa: ${nazwaTriceratopsa}`); // TypeScript wie, że to string
30
31const wiekTriceratopsa = pobierzWłaściwośćDinozaura(triceratops, "wiek");
32console.log(`Wiek: ${wiekTriceratopsa} lat`); // TypeScript wie, że to number
33
34const cechy = pobierzWłaściwośćDinozaura(triceratops, "cechy");
35console.log(`Cechy: ${cechy.join(", ")}`); // TypeScript wie, że to string[]
36
37// To nie zadziała, ponieważ "nieistniejącaWłaściwość" nie jest kluczem DaneDinozaura
38// pobierzWłaściwośćDinozaura(triceratops, "nieistniejącaWłaściwość");

W tym przykładzie:

  1. Tworzymy funkcję
    pobierzWłaściwośćDinozaura
    , która przyjmuje obiekt typu
    T
    i klucz
    K
    , ograniczony do kluczy
    T
  2. Funkcja zwraca wartość właściwości o danym kluczu, a TypeScript wie dokładnie, jakiego typu będzie zwrócona wartość
  3. Użycie funkcji z nieistniejącym kluczem zostanie wychwycone przez TypeScript na etapie kompilacji

Domyślne parametry typu

Podobnie jak funkcje mogą mieć wartości domyślne dla swoich parametrów, funkcje generyczne mogą mieć domyślne typy dla parametrów typu:

1// Funkcja generyczna z domyślnym parametrem typu
2function utworzRaportDinozaura<T = DaneDinozaura>(dane: T, etykieta: string = "Raport standardowy"): { dane: T, etykieta: string, timestamp: Date } {
3  return {
4    dane,
5    etykieta,
6    timestamp: new Date()
7  };
8}
9
10// Użycie z domyślnym typem
11const raportStandardowy = utworzRaportDinozaura(triceratops);
12console.log(`Raport: ${raportStandardowy.etykieta}`);
13console.log(`Dane dla: ${raportStandardowy.dane.nazwa}`);
14
15// Użycie z innym typem
16interface DaneKlimatyczne {
17  temperatura: number;
18  wilgotność: number;
19  ciśnienie: number;
20  lokalizacja: string;
21}
22
23const daneKlimatyczneWybiegu: DaneKlimatyczne = {
24  temperatura: 28.5,
25  wilgotność: 72,
26  ciśnienie: 1013,
27  lokalizacja: "Wybieg Triceratopsów"
28};
29
30const raportKlimatyczny = utworzRaportDinozaura<DaneKlimatyczne>(
31  daneKlimatyczneWybiegu,
32  "Raport klimatyczny"
33);
34
35console.log(`Raport: ${raportKlimatyczny.etykieta}`);
36console.log(`Temperatura: ${raportKlimatyczny.dane.temperatura}°C`);

Generyczne typy tablicowe i operacje na kolekcjach

Funkcje generyczne są szczególnie przydatne przy pracy z tablicami i kolekcjami danych:

1// Funkcja generyczna do filtrowania tablicy
2function filtrujKolekcję<T>(kolekcja: T[], predykat: (element: T) => boolean): T[] {
3  return kolekcja.filter(predykat);
4}
5
6// Przykładowe dane dinozaurów
7interface KrótkaInformacjaDinozaura {
8  id: string;
9  nazwa: string;
10  gatunek: string;
11  drapieżnik: boolean;
12  waga: number;
13}
14
15const dinozaury: KrótkaInformacjaDinozaura[] = [
16  { id: "TREX-001", nazwa: "Rex", gatunek: "Tyrannosaurus", drapieżnik: true, waga: 8000 },
17  { id: "VEL-001", nazwa: "Blue", gatunek: "Velociraptor", drapieżnik: true, waga: 150 },
18  { id: "TRIC-001", nazwa: "Tricky", gatunek: "Triceratops", drapieżnik: false, waga: 9000 },
19  { id: "STEG-001", nazwa: "Spike", gatunek: "Stegosaurus", drapieżnik: false, waga: 5000 },
20  { id: "BRACH-001", nazwa: "Brachie", gatunek: "Brachiosaurus", drapieżnik: false, waga: 40000 }
21];
22
23// Filtrowanie dinozaurów drapieżnych
24const drapieżniki = filtrujKolekcję<KrótkaInformacjaDinozaura>(
25  dinozaury,
26  dino => dino.drapieżnik
27);
28
29console.log("Drapieżniki w parku:");
30drapieżniki.forEach(dino => console.log(`- ${dino.nazwa} (${dino.gatunek})`));
31
32// Filtrowanie dinozaurów cięższych niż 10000 kg
33const ciężkieDinozaury = filtrujKolekcję<KrótkaInformacjaDinozaura>(
34  dinozaury,
35  dino => dino.waga > 10000
36);
37
38console.log("
39Ciężkie dinozaury:");
40ciężkieDinozaury.forEach(dino => console.log(`- ${dino.nazwa}: ${dino.waga} kg`));
41
42// Bardziej zaawansowana funkcja generyczna do mapowania tablicy
43function przekształćKolekcję<T, U>(kolekcja: T[], transformacja: (element: T) => U): U[] {
44  return kolekcja.map(transformacja);
45}
46
47// Tworzenie prostszych obiektów na podstawie dinozaurów
48const etykietyDinozaurów = przekształćKolekcję<KrótkaInformacjaDinozaura, { nazwa: string, typ: string }>(
49  dinozaury,
50  dino => ({
51    nazwa: dino.nazwa,
52    typ: dino.drapieżnik ? "drapieżnik" : "roślinożerca"
53  })
54);
55
56console.log("
57Etykiety dinozaurów:");
58etykietyDinozaurów.forEach(etykieta => console.log(`- ${etykieta.nazwa}: ${etykieta.typ}`));

Generyczne klasy i metody

Nie tylko funkcje mogą być generyczne - klasy i ich metody również mogą wykorzystywać tę potężną funkcjonalność:

1// Generyczna klasa Kolekcja - może przechowywać dane dowolnego typu
2class Kolekcja<T> {
3  private elementy: T[] = [];
4
5  // Metoda dodająca element do kolekcji
6  dodaj(element: T): void {
7    this.elementy.push(element);
8  }
9
10  // Metoda pobierająca element o danym indeksie
11  pobierz(indeks: number): T | undefined {
12    if (indeks >= 0 && indeks < this.elementy.length) {
13      return this.elementy[indeks];
14    }
15    return undefined;
16  }
17
18  // Metoda zwracająca wszystkie elementy
19  wszystkie(): T[] {
20    return [...this.elementy];
21  }
22
23  // Metoda filtrująca elementy
24  filtruj(predykat: (element: T) => boolean): T[] {
25    return this.elementy.filter(predykat);
26  }
27
28  // Generyczna metoda mapująca do innego typu
29  mapuj<U>(transformacja: (element: T) => U): U[] {
30    return this.elementy.map(transformacja);
31  }
32
33  // Metoda zwracająca liczbę elementów
34  liczbaElementów(): number {
35    return this.elementy.length;
36  }
37}
38
39// Przykład użycia z dinozaurami
40const kolekcjaDinozaurów = new Kolekcja<KrótkaInformacjaDinozaura>();
41
42// Dodawanie dinozaurów do kolekcji
43dinozaury.forEach(dino => kolekcjaDinozaurów.dodaj(dino));
44
45// Pobieranie konkretnego dinozaura
46const pierwszy = kolekcjaDinozaurów.pobierz(0);
47if (pierwszy) {
48  console.log(`Pierwszy dinozaur: ${pierwszy.nazwa} (${pierwszy.gatunek})`);
49}
50
51// Filtrowanie kolekcji
52const małeDinozaury = kolekcjaDinozaurów.filtruj(dino => dino.waga < 1000);
53console.log(`Liczba małych dinozaurów: ${małeDinozaury.length}`);
54
55// Mapowanie do raportów zdrowia
56interface RaportZdrowia {
57  id: string;
58  nazwa: string;
59  statusZdrowia: string;
60  uwagi: string;
61}
62
63const raportyZdrowia = kolekcjaDinozaurów.mapuj<RaportZdrowia>(dino => ({
64  id: dino.id,
65  nazwa: dino.nazwa,
66  statusZdrowia: "dobry", // Symulacja - w rzeczywistości pobieralibyśmy dane z bazy
67  uwagi: dino.drapieżnik
68    ? "Zachować szczególną ostrożność"
69    : "Standardowe procedury"
70}));
71
72console.log("
73Raporty zdrowia:");
74raportyZdrowia.forEach(raport => {
75  console.log(`- ${raport.nazwa}: ${raport.statusZdrowia} (${raport.uwagi})`);
76});

Generyczne interfejsy i typy

Podobnie jak funkcje i klasy, również interfejsy i typy mogą być generyczne:

1// Generyczny interfejs dla systemu przechowywania
2interface System<T> {
3  dodaj(element: T): void;
4  pobierz(id: string): T | null;
5  aktualizuj(id: string, element: T): boolean;
6  usuń(id: string): boolean;
7  wszystkie(): T[];
8}
9
10// Implementacja generycznego systemu przechowywania dla dinozaurów
11class BazaDanychDinozaurów<T extends { id: string }> implements System<T> {
12  private dane: Map<string, T> = new Map();
13
14  dodaj(element: T): void {
15    this.dane.set(element.id, element);
16  }
17
18  pobierz(id: string): T | null {
19    return this.dane.has(id) ? this.dane.get(id)! : null;
20  }
21
22  aktualizuj(id: string, element: T): boolean {
23    if (this.dane.has(id)) {
24      this.dane.set(id, element);
25      return true;
26    }
27    return false;
28  }
29
30  usuń(id: string): boolean {
31    return this.dane.delete(id);
32  }
33
34  wszystkie(): T[] {
35    return Array.from(this.dane.values());
36  }
37}
38
39// Definiowanie typów z użyciem generycznego typu
40type DaneZPrzypisanym<T> = {
41  dane: T;
42  przypisanyPracownik: string;
43  ostatniaAktualizacja: Date;
44};
45
46// Użycie generycznego typu
47type DinozaurZOpiekunem = DaneZPrzypisanym<KrótkaInformacjaDinozaura>;
48
49// Tworzenie bazy danych dla dinozaurów z przypisanymi opiekunami
50const bazaDanychZOpiekunami = new BazaDanychDinozaurów<DinozaurZOpiekunem>();
51
52// Dodawanie rekordów
53bazaDanychZOpiekunami.dodaj({
54  dane: dinozaury[0],
55  przypisanyPracownik: "Owen Grady",
56  ostatniaAktualizacja: new Date(),
57  id: dinozaury[0].id
58});
59
60bazaDanychZOpiekunami.dodaj({
61  dane: dinozaury[1],
62  przypisanyPracownik: "Dr. Henry Wu",
63  ostatniaAktualizacja: new Date(),
64  id: dinozaury[1].id
65});
66
67// Pobieranie danych
68const rexZOpiekunem = bazaDanychZOpiekunami.pobierz("TREX-001");
69if (rexZOpiekunem) {
70  console.log(`
71Dinozaur ${rexZOpiekunem.dane.nazwa} jest pod opieką ${rexZOpiekunem.przypisanyPracownik}`);
72  console.log(`Ostatnia aktualizacja: ${rexZOpiekunem.ostatniaAktualizacja.toLocaleDateString()}`);
73}

Praktyczny przykład: System zarządzania parkiem

Przyjrzyjmy się kompleksowemu przykładowi, jak funkcje generyczne mogą być wykorzystane w systemie zarządzania Parkiem Jurajskim:

1// ===== SYSTEM ZARZĄDZANIA PARKIEM JURAJSKIM =====
2
3// Podstawowe interfejsy
4interface ElementParku {
5  id: string;
6  nazwa: string;
7  typElementu: string;
8}
9
10interface ObszarParku extends ElementParku {
11  typElementu: "obszar";
12  współrzędne: [number, number];
13  powierzchnia: number; // w hektarach
14  poziomBezpieczeństwa: 1 | 2 | 3 | 4 | 5;
15}
16
17interface WybiegDinozaurów extends ObszarParku {
18  typElementu: "wybieg";
19  mieszkańcy: string[]; // ID dinozaurów
20  typŚrodowiska: "las" | "sawanna" | "bagno" | "wybrzeże" | "akwen";
21  statusOgrodzenia: "aktywne" | "nieaktywne" | "w naprawie";
22}
23
24interface DinozaurParku extends ElementParku {
25  typElementu: "dinozaur";
26  gatunek: string;
27  wiek: number;
28  waga: number;
29  drapieżnik: boolean;
30  lokalizacja: string; // ID obszaru
31  statusZdrowia: "doskonały" | "dobry" | "wymaga uwagi" | "chory" | "krytyczny";
32}
33
34interface PracownikParku extends ElementParku {
35  typElementu: "pracownik";
36  stanowisko: string;
37  poziomDostępu: 1 | 2 | 3 | 4 | 5;
38  specjalizacja: string[];
39}
40
41// Generyczna klasa repozytorium
42class Repozytorium<T extends ElementParku> {
43  private elementy: Map<string, T> = new Map();
44
45  dodaj(element: T): void {
46    this.elementy.set(element.id, element);
47  }
48
49  pobierz(id: string): T | undefined {
50    return this.elementy.get(id);
51  }
52
53  wszystkie(): T[] {
54    return Array.from(this.elementy.values());
55  }
56
57  aktualizuj(id: string, dane: Partial<T>): boolean {
58    if (this.elementy.has(id)) {
59      const element = this.elementy.get(id)!;
60      this.elementy.set(id, { ...element, ...dane });
61      return true;
62    }
63    return false;
64  }
65
66  usuń(id: string): boolean {
67    return this.elementy.delete(id);
68  }
69
70  filtruj(predykat: (element: T) => boolean): T[] {
71    return this.wszystkie().filter(predykat);
72  }
73}
74
75// Generyczna funkcja do znajdowania powiązań między elementami
76function znajdźPowiązania<T extends ElementParku, U extends ElementParku>(
77  źródła: T[],
78  cele: U[],
79  predykat: (źródło: T, cel: U) => boolean
80): Array<[T, U[]]> {
81  return źródła.map(źródło => {
82    const powiązaneCele = cele.filter(cel => predykat(źródło, cel));
83    return [źródło, powiązaneCele] as [T, U[]];
84  });
85}
86
87// Generyczna funkcja do tworzenia raportów
88function generujRaport<T extends ElementParku>(
89  nazwa: string,
90  elementy: T[],
91  formatujElement: (element: T) => string
92): string {
93  let raport = `=== ${nazwa} ===
94`;
95  raport += `Data wygenerowania: ${new Date().toLocaleString()}
96`;
97  raport += `Liczba elementów: ${elementy.length}
98
99`;
100
101  if (elementy.length === 0) {
102    raport += "Brak danych do wyświetlenia.
103";
104  } else {
105    elementy.forEach(element => {
106      raport += formatujElement(element) + "
107";
108    });
109  }
110
111  return raport;
112}
113
114// Symulacja danych parku
115// Tworzenie repozytoriów
116const repoDinozaurów = new Repozytorium<DinozaurParku>();
117const repoObszarów = new Repozytorium<ObszarParku>();
118const repoWybiegów = new Repozytorium<WybiegDinozaurów>();
119const repoPracowników = new Repozytorium<PracownikParku>();
120
121// Dodawanie danych do repozytoriów
122// Obszary
123const obszarCentralny: ObszarParku = {
124  id: "OBS-001",
125  nazwa: "Obszar Centralny",
126  typElementu: "obszar",
127  współrzędne: [20.123, -75.456],
128  powierzchnia: 25,
129  poziomBezpieczeństwa: 1
130};
131repoObszarów.dodaj(obszarCentralny);
132
133// Wybiegi
134const wybiegTRex: WybiegDinozaurów = {
135  id: "WYB-001",
136  nazwa: "Wybieg T-Rex",
137  typElementu: "wybieg",
138  współrzędne: [20.145, -75.460],
139  powierzchnia: 8,
140  poziomBezpieczeństwa: 5,
141  mieszkańcy: ["DINO-001"],
142  typŚrodowiska: "las",
143  statusOgrodzenia: "aktywne"
144};
145repoWybiegów.dodaj(wybiegTRex);
146
147const wybiegRaptory: WybiegDinozaurów = {
148  id: "WYB-002",
149  nazwa: "Wybieg Raptorów",
150  typElementu: "wybieg",
151  współrzędne: [20.150, -75.465],
152  powierzchnia: 5,
153  poziomBezpieczeństwa: 5,
154  mieszkańcy: ["DINO-002", "DINO-003", "DINO-004", "DINO-005"],
155  typŚrodowiska: "sawanna",
156  statusOgrodzenia: "aktywne"
157};
158repoWybiegów.dodaj(wybiegRaptory);
159
160// Dinozaury
161const trex: DinozaurParku = {
162  id: "DINO-001",
163  nazwa: "Rexy",
164  typElementu: "dinozaur",
165  gatunek: "Tyrannosaurus Rex",
166  wiek: 7,
167  waga: 8000,
168  drapieżnik: true,
169  lokalizacja: "WYB-001",
170  statusZdrowia: "dobry"
171};
172repoDinozaurów.dodaj(trex);
173
174const raptor1: DinozaurParku = {
175  id: "DINO-002",
176  nazwa: "Blue",
177  typElementu: "dinozaur",
178  gatunek: "Velociraptor",
179  wiek: 4,
180  waga: 150,
181  drapieżnik: true,
182  lokalizacja: "WYB-002",
183  statusZdrowia: "doskonały"
184};
185repoDinozaurów.dodaj(raptor1);
186
187// Pracownicy
188const trener: PracownikParku = {
189  id: "PRAC-001",
190  nazwa: "Owen Grady",
191  typElementu: "pracownik",
192  stanowisko: "Treser Raptorów",
193  poziomDostępu: 4,
194  specjalizacja: ["Behawioryzm", "Tresura dinozaurów", "Operacje terenowe"]
195};
196repoPracowników.dodaj(trener);
197
198const naukowiec: PracownikParku = {
199  id: "PRAC-002",
200  nazwa: "Dr. Henry Wu",
201  typElementu: "pracownik",
202  stanowisko: "Główny Genetyk",
203  poziomDostępu: 5,
204  specjalizacja: ["Genetyka", "Inżynieria genetyczna", "Klonowanie"]
205};
206repoPracowników.dodaj(naukowiec);
207
208// Przykładowe użycie generycznych funkcji
209
210// 1. Znajdowanie dinozaurów w określonych wybiegach
211const dinozauryWWybiegach = znajdźPowiązania<WybiegDinozaurów, DinozaurParku>(
212  repoWybiegów.wszystkie(),
213  repoDinozaurów.wszystkie(),
214  (wybieg, dino) => wybieg.mieszkańcy.includes(dino.id)
215);
216
217// 2. Generowanie raportu o stanie wybiegów
218const raportWybiegów = generujRaport<WybiegDinozaurów>(
219  "RAPORT STANU WYBIEGÓW",
220  repoWybiegów.wszystkie(),
221  wybieg => {
222    const liczbaMieszkańców = wybieg.mieszkańcy.length;
223    return `${wybieg.nazwa} (ID: ${wybieg.id})
224    - Typ środowiska: ${wybieg.typŚrodowiska}
225    - Powierzchnia: ${wybieg.powierzchnia} ha
226    - Status ogrodzenia: ${wybieg.statusOgrodzenia}
227    - Poziom bezpieczeństwa: ${wybieg.poziomBezpieczeństwa}
228    - Liczba mieszkańców: ${liczbaMieszkańców}`;
229  }
230);
231
232// 3. Generowanie raportu o stanie zdrowia dinozaurów
233const raportZdrowiaDinozaurów = generujRaport<DinozaurParku>(
234  "RAPORT ZDROWIA DINOZAURÓW",
235  repoDinozaurów.wszystkie(),
236  dino => {
237    const lokalizacja = repoWybiegów.pobierz(dino.lokalizacja)?.nazwa || "Nieznana";
238    return `${dino.nazwa} (${dino.gatunek})
239    - ID: ${dino.id}
240    - Wiek: ${dino.wiek} lat
241    - Waga: ${dino.waga} kg
242    - Status zdrowia: ${dino.statusZdrowia}
243    - Lokalizacja: ${lokalizacja}`;
244  }
245);
246
247// Wyświetlanie raportów
248console.log("=== DINOZAURY W WYBIEGACH ===");
249dinozauryWWybiegach.forEach(([wybieg, dinozaury]) => {
250  console.log(`Wybieg: ${wybieg.nazwa}`);
251  if (dinozaury.length === 0) {
252    console.log("  Brak mieszkańców");
253  } else {
254    dinozaury.forEach(dino => {
255      console.log(`  - ${dino.nazwa} (${dino.gatunek}): ${dino.statusZdrowia}`);
256    });
257  }
258});
259
260console.log("
261" + raportWybiegów);
262console.log("
263" + raportZdrowiaDinozaurów);

Podsumowanie

Funkcje generyczne to niezwykle potężne narzędzie w TypeScript, które pozwala na tworzenie elastycznych, wielokrotnego użytku komponentów, zachowując jednocześnie pełne bezpieczeństwo typów. Podobnie jak zaawansowane techniki genetyczne Dr. Wu, funkcje generyczne pozwalają na tworzenie wysoce adaptacyjnych rozwiązań, które mogą działać z różnymi typami danych.

| Technika | Zastosowanie w Parku Jurajskim | |----------|----------------------------------| | Podstawowe funkcje generyczne | Obsługa różnych typów danych dinozaurów | | Wieloparametrowe funkcje generyczne | Łączenie danych o dinozaurach z różnych źródeł | | Ograniczenia generyczne | Zapewnienie, że funkcje działają tylko z odpowiednimi typami dinozaurów | | Ograniczenia z

keyof
| Bezpieczny dostęp do właściwości dinozaurów | | Domyślne parametry typu | Uproszczenie pracy z najczęściej używanymi typami danych | | Generyczne operacje na kolekcjach | Efektywne zarządzanie grupami dinozaurów | | Generyczne klasy i metody | Tworzenie elastycznych systemów zarządzania okazami | | Generyczne interfejsy i typy | Definiowanie spójnych kontraktów dla różnych podsystemów parku |

Dr. Wu podsumowuje: "Tak jak nasze przełomowe techniki genetyczne pozwoliły nam przywrócić do życia gatunki, które zniknęły z Ziemi 65 milionów lat temu, tak generyczne funkcje TypeScript pozwalają programistom tworzyć elastyczne, adaptacyjne rozwiązania, które mogą ewoluować wraz z potrzebami systemu. Pamiętaj jednak, że z wielką mocą wiąże się wielka odpowiedzialność - zarówno w inżynierii genetycznej, jak i w programowaniu."

Ir a CodeWorlds