Kurs JavaScript i TypeScript · Moduł 6: Podstawy TypeScript

Przeciążanie funkcji

12 min czytania
W tej lekcji9

"W Parku Jurajskim precyzja jest kluczowa," mówi Dr. Henry Wu, patrząc na swój wysoce wyspecjalizowany sprzęt laboratoryjny. "Ten sam analizator DNA może badać próbki w różnych formatach - od krwi dinozaurów, przez skamieniałości, po bursztyn. Podobnie w TypeScript, jedna funkcja może obsługiwać różne zestawy parametrów i zwracać różne typy danych."

Witaj w świecie przeciążania funkcji (function overloading) - potężnego mechanizmu TypeScript, który pozwala na definiowanie funkcji przyjmujących różne rodzaje parametrów i zwracających różne typy wyników.

Czym jest przeciążanie funkcji?

Przeciążanie funkcji to mechanizm, który umożliwia definiowanie wielu sygnatur dla jednej funkcji. W ten sposób możemy stworzyć funkcję, która reaguje różnie w zależności od liczby, typów i struktury przekazanych parametrów.

Innymi słowy, tak jak naukowcy w Parku Jurajskim mogą wykorzystywać ten sam sprzęt do różnych procedur, tak my możemy wykorzystać jedną funkcję do różnych operacji, zachowując przy tym pełne bezpieczeństwo typów.

Podstawy przeciążania funkcji

W TypeScript przeciążanie funkcji składa się z dwóch części:

  1. Sygnatury przeciążeń (overload signatures) - definicje typów parametrów i zwracanych wartości
  2. Sygnatura implementacji (implementation signature) - faktyczna implementacja funkcji, która musi być kompatybilna ze wszystkimi sygnaturami przeciążeń

Przyjrzyjmy się prostemu przykładowi:

1// Sygnatury przeciążeń
2function analizujPróbkę(id: number): string;
3function analizujPróbkę(nazwa: string): string;
4
5// Sygnatura implementacji
6function analizujPróbkę(argument: number | string): string {
7  if (typeof argument === "number") {
8    return `Analizowanie próbki o ID: ${argument}`;
9  } else {
10    return `Analizowanie próbki: ${argument}`;
11  }
12}
13
14// Użycie funkcji
15const wynik1 = analizujPróbkę(42);         // TypeScript wie, że argument to number
16const wynik2 = analizujPróbkę("Raptor-01"); // TypeScript wie, że argument to string
17// const błąd = analizujPróbkę(true);      // Błąd, boolean nie jest akceptowanym typem

W powyższym przykładzie:

  1. Zdefiniowaliśmy dwie sygnatury przeciążeń - jedna przyjmująca number, druga przyjmująca string
  2. Stworzyliśmy jedną implementację, która obsługuje oba typy
  3. TypeScript dba o to, aby funkcja była wywoływana tylko z argumentami zgodnych typów

Przeciążanie z różnymi typami zwracanymi

Prawdziwa moc przeciążania ujawnia się, gdy funkcja może zwracać różne typy w zależności od parametrów wejściowych:

1// Sygnatury przeciążeń z różnymi typami zwracanymi
2function pobierzDaneDinozaura(id: number): { id: number; gatunek: string; wiek: number };
3function pobierzDaneDinozaura(nazwa: string): { nazwa: string; gatunek: string };
4
5// Sygnatura implementacji
6function pobierzDaneDinozaura(identyfikator: number | string): any {
7  if (typeof identyfikator === "number") {
8    // Pobieranie pełnych danych na podstawie ID
9    return {
10      id: identyfikator,
11      gatunek: "Tyrannosaurus",
12      wiek: 7
13    };
14  } else {
15    // Pobieranie podstawowych informacji na podstawie nazwy
16    return {
17      nazwa: identyfikator,
18      gatunek: "Velociraptor"
19    };
20  }
21}
22
23// Użycie z pełną informacją o typach
24const dinozaurPoId = pobierzDaneDinozaura(42);
25console.log(dinozaurPoId.wiek);  // OK, TypeScript wie, że właściwość 'wiek' istnieje
26
27const dinozaurPoNazwie = pobierzDaneDinozaura("Blue");
28// console.log(dinozaurPoNazwie.wiek);  // Błąd, TypeScript wie, że obiekt nie ma właściwości 'wiek'
29console.log(dinozaurPoNazwie.gatunek);  // OK

Zwróć uwagę, że mimo użycia any w sygnaturze implementacji, TypeScript zapewnia pełne bezpieczeństwo typów podczas korzystania z funkcji, bazując na sygnaturach przeciążeń.

Przeciążanie z różną liczbą parametrów

Możemy również przeciążać funkcje, które przyjmują różną liczbę parametrów:

1// Przeciążenie z różną liczbą parametrów
2function monitorujDinozaura(id: number): string;
3function monitorujDinozaura(id: number, poziomSzczegółów: "podstawowy" | "pełny"): Object;
4function monitorujDinozaura(id: number, poziomSzczegółów?: "podstawowy" | "pełny"): string | Object {
5  if (!poziomSzczegółów) {
6    return `Monitorowanie dinozaura o ID ${id}...`;
7  } else if (poziomSzczegółów === "podstawowy") {
8    return {
9      status: "aktywny",
10      lokalizacja: "Sektor B"
11    };
12  } else {
13    return {
14      status: "aktywny",
15      lokalizacja: "Sektor B",
16      prędkość: "2 km/h",
17      tętno: 80,
18      temperaturaCiała: 38,
19      ostatniPosiłek: "2 godziny temu",
20      poziomAgresji: "niski"
21    };
22  }
23}
24
25// Użycie
26const komunikat = monitorujDinozaura(5);  // Zwraca string
27const podstawoweDane = monitorujDinozaura(5, "podstawowy");  // Zwraca prosty obiekt
28const szczegółoweDane = monitorujDinozaura(5, "pełny");  // Zwraca rozbudowany obiekt

Przeciążanie metod w klasach

Przeciążanie funkcji może być również stosowane do metod w klasach:

1class EkipaBadawcza {
2  // Sygnatury przeciążeń dla metody wyslijZespol
3  wyslijZespol(cel: { x: number, y: number }): string;
4  wyslijZespol(sektor: string): string;
5  wyslijZespol(cel: string, wielkosc: number): string;
6
7  // Implementacja metody
8  wyslijZespol(cel: { x: number, y: number } | string, wielkosc?: number): string {
9    if (typeof cel === "object") {
10      return `Wysyłanie zespołu na współrzędne: ${cel.x}, ${cel.y}`;
11    } else if (wielkosc !== undefined) {
12      return `Wysyłanie zespołu ${wielkosc}-osobowego do sektora: ${cel}`;
13    } else {
14      return `Wysyłanie standardowego zespołu do sektora: ${cel}`;
15    }
16  }
17}
18
19const ekipa = new EkipaBadawcza();
20console.log(ekipa.wyslijZespol({ x: 123, y: 456 }));  // Użycie współrzędnych
21console.log(ekipa.wyslijZespol("B-5"));  // Użycie nazwy sektora
22console.log(ekipa.wyslijZespol("C-7", 5));  // Użycie nazwy sektora i wielkości zespołu

Zaawansowane przykłady przeciążania funkcji

Różne typy dla różnych kombinacji parametrów

1// Sygnatury przeciążeń dla złożonej analizy
2function analizujZachowanie(
3  gatunekId: number,
4  daneBehawioralne: number[]
5): { poziomAgresji: number; ryzyko: string };
6
7function analizujZachowanie(
8  gatunekId: number,
9  daneBehawioralne: number[],
10  historyczneDane: boolean
11): { poziomAgresji: number; ryzyko: string; porównanie: string };
12
13// Implementacja
14function analizujZachowanie(
15  gatunekId: number,
16  daneBehawioralne: number[],
17  historyczneDane?: boolean
18): any {
19  // Podstawowa analiza
20  const średniaAktywność = daneBehawioralne.reduce((a, b) => a + b, 0) / daneBehawioralne.length;
21  const poziomAgresji = Math.round(średniaAktywność * 10) / 10;
22
23  let ryzyko = "niskie";
24  if (poziomAgresji > 7) ryzyko = "wysokie";
25  else if (poziomAgresji > 4) ryzyko = "średnie";
26
27  // Podstawowy wynik
28  const wynik = { poziomAgresji, ryzyko };
29
30  // Rozszerzony wynik jeśli żądane są dane historyczne
31  if (historyczneDane) {
32    // Symulacja pobrania danych historycznych
33    const historycznyPoziom = 3.5;
34    const zmiana = poziomAgresji - historycznyPoziom;
35    const porównanie = zmiana > 0
36      ? `Wzrost o ${zmiana.toFixed(1)} pkt`
37      : `Spadek o ${Math.abs(zmiana).toFixed(1)} pkt`;
38
39    return { ...wynik, porównanie };
40  }
41
42  return wynik;
43}
44
45// Użycie
46const podstawowaAnaliza = analizujZachowanie(1, [2, 4, 3, 6, 1]);
47console.log(podstawowaAnaliza.ryzyko);  // "średnie"
48
49const rozszerzonaAnaliza = analizujZachowanie(1, [2, 4, 3, 6, 1], true);
50console.log(rozszerzonaAnaliza.porównanie);  // "Wzrost o 0.8 pkt"

Generyczne przeciążenia funkcji

Przeciążenia funkcji mogą być jeszcze potężniejsze, gdy połączymy je z generycznymi typami:

1// Generyczne przeciążenia funkcji
2function pobierzDane<T extends { id: number }>(id: number): T;
3function pobierzDane<T extends { nazwa: string }>(nazwa: string): T;
4function pobierzDane<T>(identyfikator: number | string): T {
5  // Implementacja komunikująca się z bazą danych i zwracająca odpowiedni typ
6  if (typeof identyfikator === "number") {
7    // Symulacja różnych typów danych dla różnych ID
8    if (identyfikator < 100) {
9      // Dinozaur
10      return {
11        id: identyfikator,
12        gatunek: "Tyrannosaurus",
13        wiek: 7,
14        waga: 7500
15      } as unknown as T;
16    } else {
17      // Pracownik
18      return {
19        id: identyfikator,
20        imie: "Owen",
21        nazwisko: "Grady",
22        stanowisko: "Treser"
23      } as unknown as T;
24    }
25  } else {
26    // Wyszukiwanie po nazwie
27    if (identyfikator.includes("REX")) {
28      return {
29        nazwa: identyfikator,
30        gatunek: "Tyrannosaurus",
31        populacja: 1
32      } as unknown as T;
33    } else {
34      return {
35        nazwa: identyfikator,
36        typ: "Sektor",
37        pojemność: 12
38      } as unknown as T;
39    }
40  }
41}
42
43// Interfejsy do typowania
44interface Dinozaur {
45  id: number;
46  gatunek: string;
47  wiek: number;
48  waga: number;
49}
50
51interface Pracownik {
52  id: number;
53  imie: string;
54  nazwisko: string;
55  stanowisko: string;
56}
57
58interface Gatunek {
59  nazwa: string;
60  gatunek: string;
61  populacja: number;
62}
63
64// Użycie z jawnym określeniem typu generycznego
65const trex = pobierzDane<Dinozaur>(1);
66console.log(trex.wiek);  // OK, TypeScript wie, że to Dinozaur
67
68const pracownik = pobierzDane<Pracownik>(101);
69console.log(pracownik.stanowisko);  // OK, TypeScript wie, że to Pracownik
70
71const gatunek = pobierzDane<Gatunek>("T-REX-01");
72console.log(gatunek.populacja);  // OK, TypeScript wie, że to Gatunek

Dobre praktyki i pułapki

1. Sygnatura implementacji musi być kompatybilna z wszystkimi sygnaturami przeciążeń

Parametry w sygnaturze implementacji muszą być tak zdefiniowane, aby mogły obsłużyć wszystkie możliwe kombinacje z sygnatur przeciążeń:

1// DOBRZE:
2function testuj(a: string): number;
3function testuj(a: number, b: boolean): string;
4function testuj(a: string | number, b?: boolean): number | string {
5  // Implementacja
6  return 0 as any;
7}
8
9// ŹLE:
10function zle(a: string): number;
11function zle(a: number, b: boolean): string;
12// Błąd: parametr 'b' w implementacji musi być opcjonalny
13function zle(a: string | number, b: boolean): number | string {
14  return 0 as any;
15}

2. Sygnatury przeciążeń są sprawdzane po kolei

TypeScript sprawdza sygnatury przeciążeń od góry do dołu, więc bardziej specyficzne sygnatury powinny być definiowane przed bardziej ogólnymi:

1// ŹLE - bardziej ogólna sygnatura przesłania bardziej szczegółową
2function niewłaściwaKolejność(dane: any[]): number;
3function niewłaściwaKolejność(dane: number[]): string;  // Ta sygnatura nigdy nie zostanie dopasowana
4function niewłaściwaKolejność(dane: any[]): number | string {
5  if (Array.isArray(dane) && dane.every(d => typeof d === "number")) {
6    return "Tablica liczb";
7  } else {
8    return dane.length;
9  }
10}
11
12// DOBRZE - bardziej specyficzna sygnatura jest pierwsza
13function właściwaKolejność(dane: number[]): string;
14function właściwaKolejność(dane: any[]): number;
15function właściwaKolejność(dane: any[]): number | string {
16  if (Array.isArray(dane) && dane.every(d => typeof d === "number")) {
17    return "Tablica liczb";
18  } else {
19    return dane.length;
20  }
21}

3. Wyważenie między przeciążaniem a parametrami opcjonalnymi i typami unii

Czasami lepszym rozwiązaniem mogą być parametry opcjonalne lub typy unii, zamiast rozbudowanych przeciążeń:

1// Zamiast wielu przeciążeń:
2function opcja1(id: number): void;
3function opcja1(id: number, tryb: string): void;
4function opcja1(id: number, tryb: string, extra: boolean): void;
5// ...i wiele więcej wersji
6
7// Lepiej użyć parametrów opcjonalnych:
8function opcja2(id: number, tryb?: string, extra?: boolean): void {
9  // Implementacja
10}

Praktyczny przykład: System zarządzania Parkiem Jurajskim

Zobaczmy kompleksowy przykład, jak przeciążanie funkcji można wykorzystać w systemie zarządzania Parkiem Jurajskim:

1// Definiowanie typów używanych w systemie
2type StatusDinozaura = "zdrowy" | "chory" | "w leczeniu" | "w śpiączce";
3type GatunekDinozaura = "Tyrannosaurus" | "Velociraptor" | "Triceratops" | "Brachiosaurus";
4type PoziomZagrożenia = 1 | 2 | 3 | 4 | 5;
5
6interface DanePodstawowe {
7  id: number;
8  nazwa: string;
9  gatunek: GatunekDinozaura;
10  status: StatusDinozaura;
11  poziomZagrożenia: PoziomZagrożenia;
12}
13
14interface DaneZdrowotne {
15  temperaturaCiała: number;
16  tętno: number;
17  czynnikStresowy: number;
18  ostatniPosiłek: Date;
19  wagaKg: number;
20}
21
22interface DaneLokalizacyjne {
23  sektor: string;
24  współrzędneGPS: [number, number];
25  ostatniaAktualizacja: Date;
26}
27
28interface RaportBezpieczeństwa {
29  statusOgrodzenia: "sprawne" | "uszkodzone" | "w naprawie";
30  zasilanieAktywne: boolean;
31  osobyWSektorze: number;
32  alertyAktywne: string[];
33}
34
35// Klasa do zarządzania dinozaurami z przeciążonymi metodami
36class SystemZarządzaniaDinozaurami {
37  private bazaDanych: Map<number, {
38    podstawowe: DanePodstawowe;
39    zdrowotne?: DaneZdrowotne;
40    lokalizacyjne?: DaneLokalizacyjne;
41    bezpieczeństwo?: RaportBezpieczeństwa;
42  }> = new Map();
43
44  // Metoda dodająca dinozaura do systemu
45  dodajDinozaura(dane: DanePodstawowe): number;
46  dodajDinozaura(dane: DanePodstawowe, zdrowotne: DaneZdrowotne): number;
47  dodajDinozaura(dane: DanePodstawowe, zdrowotne: DaneZdrowotne, lokalizacja: DaneLokalizacyjne): number;
48  dodajDinozaura(
49    dane: DanePodstawowe,
50    zdrowotne?: DaneZdrowotne,
51    lokalizacja?: DaneLokalizacyjne
52  ): number {
53    const nowyRekord = {
54      podstawowe: dane,
55      zdrowotne,
56      lokalizacyjne: lokalizacja
57    };
58
59    this.bazaDanych.set(dane.id, nowyRekord);
60    console.log(`Dodano dinozaura: ${dane.nazwa} (ID: ${dane.id})`);
61    return dane.id;
62  }
63
64  // Przeciążona metoda do pobierania danych o dinozaurze
65  pobierzDane(id: number): DanePodstawowe;
66  pobierzDane(id: number, typ: "zdrowie"): DaneZdrowotne | undefined;
67  pobierzDane(id: number, typ: "lokalizacja"): DaneLokalizacyjne | undefined;
68  pobierzDane(id: number, typ: "bezpieczeństwo"): RaportBezpieczeństwa | undefined;
69  pobierzDane(id: number, typ: "wszystko"): {
70    podstawowe: DanePodstawowe;
71    zdrowotne?: DaneZdrowotne;
72    lokalizacyjne?: DaneLokalizacyjne;
73    bezpieczeństwo?: RaportBezpieczeństwa;
74  };
75  pobierzDane(id: number, typ?: "zdrowie" | "lokalizacja" | "bezpieczeństwo" | "wszystko"): any {
76    const rekord = this.bazaDanych.get(id);
77
78    if (!rekord) {
79      throw new Error(`Dinozaur o ID ${id} nie istnieje w systemie.`);
80    }
81
82    if (!typ) {
83      return rekord.podstawowe;
84    }
85
86    switch (typ) {
87      case "zdrowie":
88        return rekord.zdrowotne;
89      case "lokalizacja":
90        return rekord.lokalizacyjne;
91      case "bezpieczeństwo":
92        return rekord.bezpieczeństwo;
93      case "wszystko":
94        return rekord;
95    }
96  }
97
98  // Przeciążona metoda do aktualizacji danych
99  aktualizujDane(id: number, dane: Partial<DanePodstawowe>): boolean;
100  aktualizujDane(id: number, dane: DaneZdrowotne, typ: "zdrowie"): boolean;
101  aktualizujDane(id: number, dane: DaneLokalizacyjne, typ: "lokalizacja"): boolean;
102  aktualizujDane(id: number, dane: RaportBezpieczeństwa, typ: "bezpieczeństwo"): boolean;
103  aktualizujDane(
104    id: number,
105    dane: Partial<DanePodstawowe> | DaneZdrowotne | DaneLokalizacyjne | RaportBezpieczeństwa,
106    typ?: "zdrowie" | "lokalizacja" | "bezpieczeństwo"
107  ): boolean {
108    const rekord = this.bazaDanych.get(id);
109
110    if (!rekord) {
111      console.error(`Dinozaur o ID ${id} nie istnieje w systemie.`);
112      return false;
113    }
114
115    if (!typ) {
116      // Aktualizacja danych podstawowych
117      rekord.podstawowe = { ...rekord.podstawowe, ...dane as Partial<DanePodstawowe> };
118      console.log(`Zaktualizowano podstawowe dane dinozaura ID: ${id}`);
119    } else {
120      switch (typ) {
121        case "zdrowie":
122          rekord.zdrowotne = { ...(rekord.zdrowotne || {}), ...dane as DaneZdrowotne };
123          console.log(`Zaktualizowano dane zdrowotne dinozaura ID: ${id}`);
124          break;
125        case "lokalizacja":
126          rekord.lokalizacyjne = { ...(rekord.lokalizacyjne || {}), ...dane as DaneLokalizacyjne };
127          console.log(`Zaktualizowano dane lokalizacyjne dinozaura ID: ${id}`);
128          break;
129        case "bezpieczeństwo":
130          rekord.bezpieczeństwo = { ...(rekord.bezpieczeństwo || {}), ...dane as RaportBezpieczeństwa };
131          console.log(`Zaktualizowano dane bezpieczeństwa dla dinozaura ID: ${id}`);
132          break;
133      }
134    }
135
136    this.bazaDanych.set(id, rekord);
137    return true;
138  }
139
140  // Przeciążona metoda do generowania raportów
141  generujRaport(): string; // Raport dla wszystkich dinozaurów
142  generujRaport(gatunek: GatunekDinozaura): string; // Raport dla wybranego gatunku
143  generujRaport(poziomZagrożenia: PoziomZagrożenia): string; // Raport dla poziomu zagrożenia
144  generujRaport(filter?: GatunekDinozaura | PoziomZagrożenia): string {
145    let dinozaury = Array.from(this.bazaDanych.values()).map(d => d.podstawowe);
146
147    if (filter) {
148      if (typeof filter === "string") {
149        // Filtrowanie po gatunku
150        dinozaury = dinozaury.filter(d => d.gatunek === filter);
151        return this.formatujRaport(`Raport dla gatunku: ${filter}`, dinozaury);
152      } else {
153        // Filtrowanie po poziomie zagrożenia
154        dinozaury = dinozaury.filter(d => d.poziomZagrożenia === filter);
155        return this.formatujRaport(`Raport dla poziomu zagrożenia: ${filter}`, dinozaury);
156      }
157    }
158
159    return this.formatujRaport("Pełny raport wszystkich dinozaurów", dinozaury);
160  }
161
162  private formatujRaport(tytuł: string, dinozaury: DanePodstawowe[]): string {
163    let raport = `=== ${tytuł} ===\n`;
164    raport += `Data: ${new Date().toLocaleString()}\n`;
165    raport += `Liczba dinozaurów: ${dinozaury.length}\n\n`;
166
167    dinozaury.forEach(d => {
168      raport += `ID: ${d.id} | ${d.nazwa} | ${d.gatunek} | Status: ${d.status} | Zagrożenie: ${d.poziomZagrożenia}\n`;
169    });
170
171    return raport;
172  }
173}
174
175// Przykładowe użycie systemu
176const system = new SystemZarządzaniaDinozaurami();
177
178// Dodawanie dinozaurów z różnymi zestawami danych
179const rexId = system.dodajDinozaura({
180  id: 1,
181  nazwa: "Rexy",
182  gatunek: "Tyrannosaurus",
183  status: "zdrowy",
184  poziomZagrożenia: 5
185});
186
187const raptorId = system.dodajDinozaura(
188  {
189    id: 2,
190    nazwa: "Blue",
191    gatunek: "Velociraptor",
192    status: "zdrowy",
193    poziomZagrożenia: 4
194  },
195  {
196    temperaturaCiała: 38.5,
197    tętno: 85,
198    czynnikStresowy: 2.1,
199    ostatniPosiłek: new Date(Date.now() - 3600000), // 1 godzina temu
200    wagaKg: 160
201  }
202);
203
204const triceratopsId = system.dodajDinozaura(
205  {
206    id: 3,
207    nazwa: "Tricy",
208    gatunek: "Triceratops",
209    status: "zdrowy",
210    poziomZagrożenia: 2
211  },
212  {
213    temperaturaCiała: 37.2,
214    tętno: 45,
215    czynnikStresowy: 1.3,
216    ostatniPosiłek: new Date(Date.now() - 7200000), // 2 godziny temu
217    wagaKg: 8500
218  },
219  {
220    sektor: "B-7",
221    współrzędneGPS: [34.5678, -118.2345],
222    ostatniaAktualizacja: new Date()
223  }
224);
225
226// Pobieranie różnych typów danych
227const rexPodstawowe = system.pobierzDane(rexId);
228console.log(`Dinozaur ${rexPodstawowe.nazwa} ma poziom zagrożenia ${rexPodstawowe.poziomZagrożenia}`);
229
230const raptorZdrowie = system.pobierzDane(raptorId, "zdrowie");
231if (raptorZdrowie) {
232  console.log(`Temperatura ciała raptora: ${raptorZdrowie.temperaturaCiała}°C`);
233}
234
235const triceratopsLokalizacja = system.pobierzDane(triceratopsId, "lokalizacja");
236if (triceratopsLokalizacja) {
237  console.log(`Triceratops znajduje się w sektorze: ${triceratopsLokalizacja.sektor}`);
238}
239
240// Aktualizacja danych
241system.aktualizujDane(rexId, { status: "w leczeniu" });
242
243system.aktualizujDane(raptorId,
244  {
245    temperaturaCiała: 39.2,
246    tętno: 95,
247    czynnikStresowy: 4.5,
248    ostatniPosiłek: new Date(),
249    wagaKg: 162
250  },
251  "zdrowie"
252);
253
254// Generowanie raportów
255console.log(system.generujRaport());
256console.log(system.generujRaport("Velociraptor"));
257console.log(system.generujRaport(5));

Podsumowanie

Przeciążanie funkcji to potężny mechanizm TypeScript, który pozwala na tworzenie elastycznych interfejsów API, zachowując jednocześnie pełne bezpieczeństwo typów. Podobnie jak naukowcy w Parku Jurajskim muszą dostosowywać swoje procedury do różnych gatunków dinozaurów, tak programiści mogą tworzyć funkcje, które dostosowują się do różnych zestawów parametrów.

Najważniejsze zasady przeciążania funkcji:

  1. Definiuj sygnatury przeciążeń przed sygnaturą implementacji
  2. Sygnatura implementacji musi być kompatybilna ze wszystkimi sygnaturami przeciążeń
  3. Bardziej szczegółowe sygnatury umieszczaj przed bardziej ogólnymi
  4. Używaj przeciążeń, gdy chcesz mieć różne typy zwracane dla różnych parametrów wejściowych
  5. Rozważ alternatywy (parametry opcjonalne, typy unii) dla prostszych przypadków

"W programowaniu, podobnie jak w genetyce dinozaurów," mówi Dr. Wu z uśmiechem, "prawdziwa moc leży w zdolności do adaptacji. Przeciążanie funkcji to jak tworzenie adaptacyjnych sekwencji DNA - ta sama funkcja może wyewoluować, by służyć różnym celom, zachowując przy tym swoją integralność i bezpieczeństwo."

Kod do tej lekcji: index.ts
1// Przeciążanie funkcji w Parku Jurajskim
2console.log("Przeciążanie funkcji TypeScript\n");
3
4// ===========================================
5// 1. Podstawowe przeciążanie
6// ===========================================
7console.log("=== PODSTAWOWE PRZECIĄŻANIE ===");
8
9// Definicje przeciążeń
10function identify(value: string): string;
11function identify(value: number): number;
12function identify(value: boolean): boolean;
13
14// Implementacja
15function identify(value: string | number | boolean): string | number | boolean {
16  return value;
17}
18
19console.log("String:", identify("T-Rex"));
20console.log("Number:", identify(8000));
21console.log("Boolean:", identify(true));
22
23// ===========================================
24// 2. Przeciążanie z różnymi parametrami
25// ===========================================
26console.log("\n=== RÓŻNE PARAMETRY ===");
27
28// Przeciążenia
29function createDinosaur(name: string): string;
30function createDinosaur(name: string, species: string): string;
31function createDinosaur(name: string, species: string, age: number): string;
32
33// Implementacja
34function createDinosaur(
35  name: string,
36  species?: string,
37  age?: number
38): string {
39  let result = `Dinosaur: ${name}`;
40  if (species) result += `, Species: ${species}`;
41  if (age !== undefined) result += `, Age: ${age}`;
42  return result;
43}
44
45console.log(createDinosaur("Rexy"));
46console.log(createDinosaur("Blue", "Velociraptor"));
47console.log(createDinosaur("Charlie", "Velociraptor", 3));
48
49// ===========================================
50// 3. Przeciążanie z różnymi zwrotami
51// ===========================================
52console.log("\n=== RÓŻNE TYPY ZWROTNE ===");
53
54interface Dinosaur {
55  name: string;
56  species: string;
57  age: number;
58}
59
60// Przeciążenia z różnymi typami zwrotnymi
61function findDinosaur(id: number): Dinosaur | undefined;
62function findDinosaur(name: string): Dinosaur[];
63
64// Implementacja
65function findDinosaur(
66  query: number | string
67): Dinosaur | undefined | Dinosaur[] {
68  const database: Dinosaur[] = [
69    { name: "Rexy", species: "T-Rex", age: 5 },
70    { name: "Blue", species: "Velociraptor", age: 3 },
71    { name: "Charlie", species: "Velociraptor", age: 3 }
72  ];
73
74  if (typeof query === "number") {
75    return database[query];
76  } else {
77    return database.filter(d => d.name.includes(query));
78  }
79}
80
81console.log("Find by ID:", findDinosaur(0));
82console.log("Find by name:", findDinosaur("Blue"));
83
84// ===========================================
85// 4. Przeciążanie z obiektami
86// ===========================================
87console.log("\n=== PRZECIĄŻANIE Z OBIEKTAMI ===");
88
89interface SearchByName {
90  type: "name";
91  name: string;
92}
93
94interface SearchBySpecies {
95  type: "species";
96  species: string;
97}
98
99interface SearchByAge {
100  type: "age";
101  minAge: number;
102  maxAge: number;
103}
104
105type SearchCriteria = SearchByName | SearchBySpecies | SearchByAge;
106
107// Przeciążenia
108function search(criteria: SearchByName): Dinosaur[];
109function search(criteria: SearchBySpecies): Dinosaur[];
110function search(criteria: SearchByAge): Dinosaur[];
111
112// Implementacja
113function search(criteria: SearchCriteria): Dinosaur[] {
114  const database: Dinosaur[] = [
115    { name: "Rexy", species: "T-Rex", age: 5 },
116    { name: "Blue", species: "Velociraptor", age: 3 },
117    { name: "Charlie", species: "Velociraptor", age: 3 },
118    { name: "Delta", species: "Velociraptor", age: 4 }
119  ];
120
121  if (criteria.type === "name") {
122    return database.filter(d => d.name === criteria.name);
123  } else if (criteria.type === "species") {
124    return database.filter(d => d.species === criteria.species);
125  } else {
126    return database.filter(
127      d => d.age >= criteria.minAge && d.age <= criteria.maxAge
128    );
129  }
130}
131
132console.log("Search by name:", search({ type: "name", name: "Blue" }));
133console.log("Search by species:", search({ type: "species", species: "Velociraptor" }));
134console.log("Search by age:", search({ type: "age", minAge: 3, maxAge: 4 }));
135
136// ===========================================
137// 5. Przeciążanie metod w klasach
138// ===========================================
139console.log("\n=== METODY W KLASACH ===");
140
141class DinosaurManager {
142  private dinosaurs: Dinosaur[] = [];
143
144  // Przeciążenia
145  add(dino: Dinosaur): void;
146  add(name: string, species: string, age: number): void;
147
148  // Implementacja
149  add(
150    dinoOrName: Dinosaur | string,
151    species?: string,
152    age?: number
153  ): void {
154    if (typeof dinoOrName === "string") {
155      this.dinosaurs.push({
156        name: dinoOrName,
157        species: species!,
158        age: age!
159      });
160    } else {
161      this.dinosaurs.push(dinoOrName);
162    }
163  }
164
165  getAll(): Dinosaur[] {
166    return [...this.dinosaurs];
167  }
168
169  // Przeciążenia get
170  get(index: number): Dinosaur | undefined;
171  get(name: string): Dinosaur | undefined;
172
173  // Implementacja
174  get(indexOrName: number | string): Dinosaur | undefined {
175    if (typeof indexOrName === "number") {
176      return this.dinosaurs[indexOrName];
177    } else {
178      return this.dinosaurs.find(d => d.name === indexOrName);
179    }
180  }
181}
182
183const manager = new DinosaurManager();
184manager.add({ name: "Rexy", species: "T-Rex", age: 5 });
185manager.add("Blue", "Velociraptor", 3);
186
187console.log("Get by index:", manager.get(0));
188console.log("Get by name:", manager.get("Blue"));
189
190// ===========================================
191// 6. Przeciążanie z Generics
192// ===========================================
193console.log("\n=== GENERICS Z PRZECIĄŻANIEM ===");
194
195// Przeciążenia
196function process<T extends string>(value: T): string;
197function process<T extends number>(value: T): number;
198function process<T extends boolean>(value: T): boolean;
199
200// Implementacja
201function process<T extends string | number | boolean>(
202  value: T
203): string | number | boolean {
204  if (typeof value === "string") {
205    return value.toUpperCase();
206  } else if (typeof value === "number") {
207    return value * 2;
208  } else {
209    return !value;
210  }
211}
212
213console.log("Process string:", process("hello"));
214console.log("Process number:", process(21));
215console.log("Process boolean:", process(true));
216
217// ===========================================
218// 7. Zaawansowane przeciążanie
219// ===========================================
220console.log("\n=== ZAAWANSOWANE PRZECIĄŻANIE ===");
221
222interface FeedOptions {
223  type: "scheduled";
224  time: Date;
225}
226
227interface EmergencyFeedOptions {
228  type: "emergency";
229  reason: string;
230}
231
232// Przeciążenia
233function feedDinosaur(
234  dinosaur: Dinosaur,
235  amount: number
236): void;
237
238function feedDinosaur(
239  dinosaur: Dinosaur,
240  amount: number,
241  options: FeedOptions
242): void;
243
244function feedDinosaur(
245  dinosaur: Dinosaur,
246  amount: number,
247  options: EmergencyFeedOptions
248): void;
249
250// Implementacja
251function feedDinosaur(
252  dinosaur: Dinosaur,
253  amount: number,
254  options?: FeedOptions | EmergencyFeedOptions
255): void {
256  if (!options) {
257    console.log(`Feeding ${dinosaur.name} with ${amount}kg`);
258  } else if (options.type === "scheduled") {
259    console.log(`Scheduled feeding for ${dinosaur.name} at ${options.time.toISOString()}`);
260  } else {
261    console.log(`Emergency feeding for ${dinosaur.name}: ${options.reason}`);
262  }
263}
264
265const trex = { name: "Rexy", species: "T-Rex", age: 5 };
266
267feedDinosaur(trex, 400);
268feedDinosaur(trex, 400, { type: "scheduled", time: new Date() });
269feedDinosaur(trex, 600, { type: "emergency", reason: "Very hungry" });
270
271// ===========================================
272// 8. Przeciążanie konstruktorów
273// ===========================================
274console.log("\n=== KONSTRUKTORY ===");
275
276class Paddock {
277  id: string;
278  name: string;
279  capacity: number;
280
281  // Przeciążenia konstruktorów (syntaktycznie)
282  constructor(id: string);
283  constructor(id: string, name: string);
284  constructor(id: string, name: string, capacity: number);
285
286  // Implementacja
287  constructor(id: string, name?: string, capacity?: number) {
288    this.id = id;
289    this.name = name || `Paddock ${id}`;
290    this.capacity = capacity || 10;
291  }
292
293  getInfo(): string {
294    return `${this.name} (ID: ${this.id}, Capacity: ${this.capacity})`;
295  }
296}
297
298const paddock1 = new Paddock("P-01");
299const paddock2 = new Paddock("P-02", "T-Rex Territory");
300const paddock3 = new Paddock("P-03", "Raptor Pen", 5);
301
302console.log(paddock1.getInfo());
303console.log(paddock2.getInfo());
304console.log(paddock3.getInfo());
305
306// ===========================================
307// 9. Przeciążanie z Promise
308// ===========================================
309console.log("\n=== PRZECIĄŻANIE Z PROMISE ===");
310
311// Przeciążenia
312function fetchData(sync: true): Dinosaur;
313function fetchData(sync: false): Promise<Dinosaur>;
314
315// Implementacja
316function fetchData(sync: boolean): Dinosaur | Promise<Dinosaur> {
317  const dino: Dinosaur = {
318    name: "Rexy",
319    species: "T-Rex",
320    age: 5
321  };
322
323  if (sync) {
324    return dino;
325  } else {
326    return Promise.resolve(dino);
327  }
328}
329
330const syncData = fetchData(true);
331console.log("Sync data:", syncData);
332
333fetchData(false).then(asyncData => {
334  console.log("Async data:", asyncData);
335});
336
337// ===========================================
338// 10. Podsumowanie
339// ===========================================
340console.log("\n=== PODSUMOWANIE ===");
341console.log("Podstawowe przeciążanie - różne sygnatury");
342console.log("Różne parametry - opcjonalne argumenty");
343console.log("Różne zwroty - typ zależy od parametrów");
344console.log("Obiekty - discriminated unions");
345console.log("Metody w klasach - przeciążanie metod");
346console.log("Generics - przeciążanie z typami generycznymi");
347console.log("Konstruktory - przeciążanie konstruktorów");
348console.log("Promise - synchroniczny vs asynchroniczny");
349
350console.log("\nPrzeciążanie funkcji 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 jest przeciążanie funkcji (function overloading) w TypeScript?

  2. 2. Ile sygnatur implementacji może mieć przeciążona funkcja w TypeScript?

To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Stwórz przeciążoną funkcję findDino, która przyjmuje id (number) lub nazwę (string) i zwraca odpowiedni wynik.

  • Klikanie w kolejności

    Ułóż elementy przeciążonej funkcji w TypeScript:

  • Edytor kodu

    Stwórz funkcję, która przyjmuje parametr typu string | number i używa typeof jako type guard do zwrócenia odpowiedniego opisu dinozaura.

  • Układanie w pionie

    Ułóż interfejsy od najbardziej ogólnego (bazowego) do najbardziej szczegółowego (rozszerzającego):

  • Klikanie w kolejności

    Ułóż elementy deklaracji przeciążonej funkcji findDino w TypeScript:

Przydatne artykuły