Kurs JavaScript i TypeScript · Moduł 6: Podstawy TypeScript
Przeciążanie funkcji
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:
- Sygnatury przeciążeń (overload signatures) - definicje typów parametrów i zwracanych wartości
- 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 typemW powyższym przykładzie:
- Zdefiniowaliśmy dwie sygnatury przeciążeń - jedna przyjmująca
number, druga przyjmującastring - Stworzyliśmy jedną implementację, która obsługuje oba typy
- 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); // OKZwróć 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 obiektPrzeciąż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łuZaawansowane 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 GatunekDobre 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:
- Definiuj sygnatury przeciążeń przed sygnaturą implementacji
- Sygnatura implementacji musi być kompatybilna ze wszystkimi sygnaturami przeciążeń
- Bardziej szczegółowe sygnatury umieszczaj przed bardziej ogólnymi
- Używaj przeciążeń, gdy chcesz mieć różne typy zwracane dla różnych parametrów wejściowych
- 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. Czym jest przeciążanie funkcji (function overloading) w TypeScript?
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: