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 abstrakcyjna | Interfejs | |
|---|---|---|
| Zawiera kod | tak: metody, inicjalizatory pól, konstruktor | nie, tylko typy |
| Istnieje w czasie działania | tak | nie, jest usuwany |
instanceof | działa | niemożliwe |
| Klasa może użyć | jednej (extends) | wielu (implements) |
| Modyfikatory dostępu | private, protected, public | tylko publiczne składowe |
| Typ dla zwykłych obiektów | tylko jeśli nie ma składowych private/protected, a wtedy instanceof zawodzi | tak, 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
newna klasie abstrakcyjnej. Nie ma go, chyba że napiszesz sprawdzenienew.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.