"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.
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.
W TypeScript przeciążanie funkcji składa się z dwóch części:
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:
number, druga przyjmująca stringPrawdziwa 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ń.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 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łu1// 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"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 GatunekParametry 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}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}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}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ł} ===
164`;
165 raport += `Data: ${new Date().toLocaleString()}
166`;
167 raport += `Liczba dinozaurów: ${dinozaury.length}
168
169`;
170
171 dinozaury.forEach(d => {
172 raport += `ID: ${d.id} | ${d.nazwa} | ${d.gatunek} | Status: ${d.status} | Zagrożenie: ${d.poziomZagrożenia}
173`;
174 });
175
176 return raport;
177 }
178}
179
180// Przykładowe użycie systemu
181const system = new SystemZarządzaniaDinozaurami();
182
183// Dodawanie dinozaurów z różnymi zestawami danych
184const rexId = system.dodajDinozaura({
185 id: 1,
186 nazwa: "Rexy",
187 gatunek: "Tyrannosaurus",
188 status: "zdrowy",
189 poziomZagrożenia: 5
190});
191
192const raptorId = system.dodajDinozaura(
193 {
194 id: 2,
195 nazwa: "Blue",
196 gatunek: "Velociraptor",
197 status: "zdrowy",
198 poziomZagrożenia: 4
199 },
200 {
201 temperaturaCiała: 38.5,
202 tętno: 85,
203 czynnikStresowy: 2.1,
204 ostatniPosiłek: new Date(Date.now() - 3600000), // 1 godzina temu
205 wagaKg: 160
206 }
207);
208
209const triceratopsId = system.dodajDinozaura(
210 {
211 id: 3,
212 nazwa: "Tricy",
213 gatunek: "Triceratops",
214 status: "zdrowy",
215 poziomZagrożenia: 2
216 },
217 {
218 temperaturaCiała: 37.2,
219 tętno: 45,
220 czynnikStresowy: 1.3,
221 ostatniPosiłek: new Date(Date.now() - 7200000), // 2 godziny temu
222 wagaKg: 8500
223 },
224 {
225 sektor: "B-7",
226 współrzędneGPS: [34.5678, -118.2345],
227 ostatniaAktualizacja: new Date()
228 }
229);
230
231// Pobieranie różnych typów danych
232const rexPodstawowe = system.pobierzDane(rexId);
233console.log(`Dinozaur ${rexPodstawowe.nazwa} ma poziom zagrożenia ${rexPodstawowe.poziomZagrożenia}`);
234
235const raptorZdrowie = system.pobierzDane(raptorId, "zdrowie");
236if (raptorZdrowie) {
237 console.log(`Temperatura ciała raptora: ${raptorZdrowie.temperaturaCiała}°C`);
238}
239
240const triceratopsLokalizacja = system.pobierzDane(triceratopsId, "lokalizacja");
241if (triceratopsLokalizacja) {
242 console.log(`Triceratops znajduje się w sektorze: ${triceratopsLokalizacja.sektor}`);
243}
244
245// Aktualizacja danych
246system.aktualizujDane(rexId, { status: "w leczeniu" });
247
248system.aktualizujDane(raptorId,
249 {
250 temperaturaCiała: 39.2,
251 tętno: 95,
252 czynnikStresowy: 4.5,
253 ostatniPosiłek: new Date(),
254 wagaKg: 162
255 },
256 "zdrowie"
257);
258
259// Generowanie raportów
260console.log(system.generujRaport());
261console.log(system.generujRaport("Velociraptor"));
262console.log(system.generujRaport(5));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:
"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."