programowanieProgramista Backend

Jak zaimplementować przeciążanie metod w klasach TypeScript, jakich błędów należy unikać przy pisaniu przeciążonych metod i jakie subtelności związane z typowaniem mogą się pojawić?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Historia pytania:

TypeScript wspiera przeciążanie metod, podobnie jak inne języki o silnym typowaniu (np. Java lub C#), jednak składnia przeciążania w TypeScript różni się koncepcyjnie. Możliwe jest zdefiniowanie wielu sygnatur, ale tylko jednej implementacji. Może to prowadzić do zamieszania wśród programistów zaznajomionych z klasycznym przeciążaniem.

Problem:

Powszechnym błędem jest próba zdefiniowania kilku metod z różnymi zestawami parametrów. W rezultacie pojawia się błąd kompilacji, ponieważ TypeScript wymaga jednej implementacji, która realizuje wszystkie warianty sygnatur.

Rozwiązanie:

Przeciążanie osiąga się przez deklarację kilku sygnatur metody, a następnie implementację, która odpowiada wszystkim wariantom. Aby poprawnie rozróżnić parametry, zwykle stosuje się type guards lub instanceof.

Przykład kodu:

class MyLogger { log(message: string): void; log(message: string, level: 'info' | 'error'): void; log(message: string, level?: 'info' | 'error'): void { const lvl = level ?? 'info'; console.log(`[${lvl}] ${message}`); } }

Kluczowe cechy:

  • Tylko jedna implementacja metody, ale kilka deklaracji-sygnatur
  • Należy zapewnić obsługę wszystkich wariantów parametrów wejściowych w implementacji
  • Typy w implementacji powinny być maksymalnie ogólne

Pytania z podtekstem.

Czy można zrealizować dwie implementacje jednej metody z różnymi zestawami parametrów?

Nie. W TypeScript dozwolona jest tylko jedna implementacja. Kilka metod o tej samej nazwie to błąd składniowy.

Jak typizować parametry rest przy przeciążaniu metod, aby nie stracić ścisłej typizacji?

Zaleca się w sygnaturach opisywać dokładne parametry, a w implementacji — maksymalnie ogólne:

class Test { doWork(a: number): void; doWork(a: string): void; doWork(a: number | string): void { //... } }

Co się stanie, jeśli zwracany typ przeciążonych sygnatur jest różny?

TypeScript zażąda, aby implementacja zwracała typ złączony (Union). W przeciwnym razie wystąpi błąd kompilacji.

class X { get(value: number): string; get(value: string): number; get(value: number | string): string | number { return typeof value === 'number' ? 'number' : 42; } }

Typowe błędy i antywzorce

  • Niezgodność implementacji z sygnaturami przeciążenia
  • Kilka oddzielnych implementacji jednej metody
  • Ignorowanie sprawdzania typów parametrów wejściowych w ciele metody

Przykład z życia

Negatywny przypadek

W produkcie próbowano zrealizować dwie metody o tej samej nazwie dla różnych typów parametrów w klasie. Po kompilacji metoda "zastępowała" ostatnie ogłoszenie, wszystkie inne wersje były ignorowane, co prowadziło do błędów.

Zalety:

  • Znany styl dla niektórych języków

Wady:

  • W TypeScript w pełni nie działa, błędy przy kompilacji
  • Zwiększone ryzyko pominięcia przypadków

Pozytywny przypadek

Zrobiono kilka sygnatur z parametrami typu union, a w metodzie obsłużono wszystkie warianty poprzez type guards. Kompilator od razu ostrzegł o problemach typowych.

Zalety:

  • Ścisła kontrola typów
  • Bezpieczeństwo
  • Łatwość testowania

Wady:

  • Wymaga więcej kodu do sprawdzenia wariantów