Menu

Klasy abstrakcyjne w TypeScript: metody, właściwości, interfejs

Klasa abstrakcyjna w TypeScript to klasa bazowa, której nie da się utworzyć przez new i która może zostawić metody do zaimplementowania podklasom. Poznaj metody i właściwości abstrakcyjne, wzorzec template method, abstrakcyjne typy konstruktorów i sytuacje, w których lepszy jest interfejs.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Klasa abstrakcyjna to klasa bazowa oznaczona jako abstract. Nie da się jej utworzyć przez new, a może deklarować metody abstract bez ciała, które każda podklasa musi zaimplementować.

Shape udostępnia describe() każdej podklasie i zmusza każdą z nich do dostarczenia area(). Typ Shape[] przechowuje dowolną mieszankę podklas.

Klasy abstrakcyjnej nie można utworzyć

Wywołanie new na klasie abstrakcyjnej to błąd kompilacji, bo obiekt miałby metody bez implementacji:

index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.

To reguła czasu kompilacji. abstract znika z wyniku, więc wyemitowana klasa Shape jest zwykłą klasą JavaScript i nieotypowany kod JavaScript nadal może wywołać new Shape(). Gdy biblioteka potrzebuje zabezpieczenia w czasie działania, konstruktor może sprawdzić new.target:

Metody i właściwości abstrakcyjne

Podklasa musi zaimplementować każdą abstrakcyjną składową albo sama być abstrakcyjna. Brak jednej z nich to błąd TS2515, który brzmi mniej więcej tak: Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape', albo TS2654 z listą wszystkich, gdy brakuje kilku.

Abstrakcyjne mogą być pola, gettery i metody, a do tego mogą być protected:

Metoda abstrakcyjna nie ma ciała; napisanie go to błąd TS1245 (Method 'x' cannot have an implementation because it is marked abstract). Klasa abstrakcyjna nie musi mieć żadnych abstrakcyjnych składowych: samo abstract po prostu blokuje bezpośrednie tworzenie instancji.

Wzorzec template method

Najczęstszy powód, by napisać klasę abstrakcyjną: klasa bazowa ustala kolejność kroków, a każda podklasa uzupełnia te kroki, które się różnią.

Klasy abstrakcyjne mogą być generyczne, jak tutaj, więc każda podklasa wybiera typ rekordu.

Abstrakcyjne typy konstruktorów

typeof Shape dla klasy abstrakcyjnej to konstruktor, którego nie wolno wywołać, więc parametr o typie typeof Shape nie może wykonać new c(). Gdy funkcja ma przyjmować klasy abstrakcyjne (na przykład żeby je zarejestrować albo sprawdzić instanceof), otypuj ją abstrakcyjną sygnaturą konstruktora:

abstract new przyjmuje zarówno klasy abstrakcyjne, jak i konkretne; zwykłe new przyjmuje tylko konkretne.

Klasa abstrakcyjna a interfejs

Klasa abstrakcyjnaInterfejs
Zawiera kodtak: metody, inicjalizatory pól, konstruktornie, tylko typy
Istnieje w czasie działaniataknie, jest usuwany
instanceofdziałaniemożliwe
Klasa może użyćjednej (extends)wielu (implements)
Modyfikatory dostępuprivate, protected, publictylko publiczne składowe
Typ dla zwykłych obiektówtylko jeśli nie ma składowych private/protected, a wtedy instanceof zawodzitak, każda wartość o tym kształcie

Praktyczna zasada: interfejs, gdy musisz tylko opisać kształt (najczęstszy przypadek, działa też dla literałów obiektowych), klasa abstrakcyjna, gdy podklasy współdzielą prawdziwą implementację i chcesz, żeby kompilator zmusił je do uzupełnienia luk. Te dwa rozwiązania dobrze się łączą: interfejs jako publiczny kontrakt, klasa abstrakcyjna jako wygodna baza, która go implementuje. Inne popularne porównanie znajdziesz w interfejsy a typy.

Częste błędy

  • Klasa abstrakcyjna tam, gdzie wystarczy interfejs. Jeśli klasa bazowa nie ma kodu, jest interfejsem z dodatkowym narzutem w czasie działania i ograniczeniem do pojedynczego dziedziczenia.
  • Próba użycia static abstract. To niedozwolone. Składowe statyczne nie mogą być abstrakcyjne.
  • Oczekiwanie błędu w czasie działania po new na klasie abstrakcyjnej. Nie ma go, chyba że napiszesz sprawdzenie new.target.
  • Wywoływanie metody abstrakcyjnej z konstruktora bazowego. Uruchamia się ona, zanim pola podklasy zostaną zainicjalizowane, więc implementacja widzi je jako undefined.

Najczęściej zadawane pytania

Czym jest klasa abstrakcyjna w TypeScript?

To klasa oznaczona jako abstract, której nie można utworzyć przez new i która może deklarować składowe abstract bez implementacji. Podklasy muszą zaimplementować każdą abstrakcyjną składową. W ten sposób jednocześnie współdzielisz kod i wymuszasz określony kształt.

Czym różni się klasa abstrakcyjna od interfejsu w TypeScript?

Klasa abstrakcyjna może zawierać prawdziwy kod (zaimplementowane metody, pola z wartościami początkowymi, konstruktor) i istnieje w czasie działania programu, więc działa na niej instanceof; klasa może rozszerzać tylko jedną. Interfejs nie ma kodu, znika podczas kompilacji, a klasa może implementować wiele interfejsów. Klasy abstrakcyjnej używaj do współdzielenia zachowania, interfejsu do opisu kształtu.

Czy klasa abstrakcyjna może mieć konstruktor w TypeScript?

Tak. Uruchamia się, gdy podklasa wywołuje super(...). Nadal nie możesz wywołać new na samej klasie abstrakcyjnej (TS2511: Cannot create an instance of an abstract class).

Czy metody statyczne mogą być abstrakcyjne w TypeScript?

Nie. abstract dotyczy tylko składowych instancji; static abstract to błąd TS1243 ('static' modifier cannot be used with 'abstract' modifier). Aby wymusić kształt statyczny, otypuj konstruktor interfejsem zawierającym statyczną składową i sprawdź klasę względem niego.

Czy klasa abstrakcyjna jest egzekwowana w czasie działania programu?

Nie. abstract jest usuwane, więc skompilowana klasa to zwykły JavaScript, a zwykły kod JavaScript nadal może wywołać na niej new. Jeśli to ważne, sprawdź new.target w konstruktorze i rzuć wyjątek.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ