Eine abstrakte Klasse ist eine als abstract markierte Basisklasse. Sie kann nicht mit new erzeugt werden, und sie kann abstract Methoden ohne Rumpf deklarieren, die jede Unterklasse implementieren muss.
Shape teilt describe() mit jeder Unterklasse und zwingt jede, area() bereitzustellen. Der Typ Shape[] fasst jede Mischung aus Unterklassen.
Eine abstrakte Klasse kann nicht instanziiert werden
new auf einer abstrakten Klasse aufzurufen ist ein Compilerfehler, denn das Objekt hätte Methoden ohne Implementierung:
index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.
Das ist eine Regel beim Kompilieren. abstract wird aus der Ausgabe entfernt, das erzeugte Shape ist also eine gewöhnliche JavaScript-Klasse, und untypisierter JavaScript-Code könnte weiterhin new Shape() aufrufen. Braucht eine Bibliothek einen Schutz zur Laufzeit, kann der Konstruktor new.target prüfen:
Abstrakte Methoden und Eigenschaften
Eine Unterklasse muss jeden abstrakten Member implementieren oder selbst abstrakt sein. Fehlt einer, ist das der Fehler TS2515, der etwa lautet Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape', oder TS2654, der alle aufzählt, wenn mehrere fehlen.
Felder, Getter und Methoden können alle abstrakt sein, und sie können protected sein:
Eine abstrakte Methode hat keinen Rumpf; einen zu schreiben ist der Fehler TS1245 (Method 'x' cannot have an implementation because it is marked abstract). Eine abstrakte Klasse muss überhaupt keine abstrakten Member haben: abstract allein verhindert nur die direkte Instanziierung.
Das Template-Method-Muster
Der häufigste Grund für eine abstrakte Klasse: Die Basisklasse legt die Reihenfolge der Schritte fest, und jede Unterklasse füllt die Schritte aus, die sich unterscheiden.
Abstrakte Klassen können generisch sein, wie hier, sodass jede Unterklasse den Datensatztyp wählt.
Abstrakte Konstruktortypen
typeof Shape ist bei einer abstrakten Klasse ein Konstruktor, den du nicht aufrufen darfst, ein Parameter vom Typ typeof Shape kann also kein new c() ausführen. Soll eine Funktion abstrakte Klassen akzeptieren (etwa um sie zu registrieren oder instanceof zu prüfen), typisierst du sie mit einer abstrakten Construct Signature:
abstract new akzeptiert abstrakte und konkrete Klassen; einfaches new akzeptiert nur konkrete.
Abstrakte Klasse vs Interface
| Abstrakte Klasse | Interface | |
|---|---|---|
| Enthält Code | ja: Methoden, Feldinitialisierer, Konstruktor | nein, nur Typen |
| Existiert zur Laufzeit | ja | nein, entfernt |
instanceof | funktioniert | nicht möglich |
| Eine Klasse kann verwenden | eine (extends) | viele (implements) |
| Zugriffsmodifikatoren | private, protected, public | nur öffentliche Member |
| Typ für einfache Objekte | nur ohne private/protected Member, und instanceof scheitert dann | ja, jeder Wert mit der Form |
Die Faustregel: ein Interface, wenn du nur eine Form beschreiben musst (der häufigste Fall, und er funktioniert auch für Objektliterale), eine abstrakte Klasse, wenn Unterklassen echte Implementierung teilen und der Compiler sie zwingen soll, die Lücken zu füllen. Beides lässt sich gut kombinieren: ein Interface für den öffentlichen Vertrag, eine abstrakte Klasse als bequeme Basis, die es implementiert. Den anderen häufigen Vergleich findest du unter interface vs type.
Häufige Fehler
- Eine abstrakte Klasse verwenden, wo ein Interface reicht. Hat die Basisklasse keinen Code, ist sie ein Interface mit zusätzlichem Gewicht zur Laufzeit und der Grenze der Einfachvererbung.
static abstractversuchen. Nicht erlaubt. Statische Member können nicht abstrakt sein.- Einen Laufzeitfehler bei
newauf einer abstrakten Klasse erwarten. Es gibt keinen, außer du schreibst die Prüfung mitnew.target. - Eine abstrakte Methode im Basiskonstruktor aufrufen. Sie läuft, bevor die Felder der Unterklasse initialisiert sind, also sieht die Implementierung sie als
undefined.
Häufig gestellte Fragen
Was ist eine abstrakte Klasse in TypeScript?
Eine als abstract markierte Klasse, die nicht mit new erzeugt werden kann und abstract Member ohne Implementierung deklarieren darf. Unterklassen müssen jeden abstrakten Member implementieren. So teilt man Code und verlangt zugleich eine Form.
Was ist der Unterschied zwischen einer abstrakten Klasse und einem Interface in TypeScript?
Eine abstrakte Klasse kann echten Code enthalten (implementierte Methoden, Felder mit Startwerten, einen Konstruktor) und existiert zur Laufzeit, also funktioniert instanceof damit; eine Klasse kann nur eine erweitern. Ein Interface hat keinen Code, wird beim Kompilieren entfernt, und eine Klasse kann viele implementieren. Nimm eine abstrakte Klasse, um Verhalten zu teilen, und ein Interface, um eine Form zu beschreiben.
Kann eine abstrakte Klasse in TypeScript einen Konstruktor haben?
Ja. Er läuft, wenn eine Unterklasse super(...) aufruft. new auf der abstrakten Klasse selbst kannst du trotzdem nicht aufrufen (TS2511: Cannot create an instance of an abstract class).
Können statische Methoden in TypeScript abstrakt sein?
Nein. abstract gilt nur für Instanzmember; static abstract ist der Fehler TS1243 ('static' modifier cannot be used with 'abstract' modifier). Um eine statische Form zu verlangen, typisierst du den Konstruktor mit einem Interface, das den statischen Member enthält, und prüfst die Klasse dagegen.
Wird eine abstrakte Klasse zur Laufzeit durchgesetzt?
Nein. abstract wird entfernt, die kompilierte Klasse ist also gewöhnliches JavaScript, und reiner JavaScript-Code könnte weiterhin new darauf aufrufen. Wenn das wichtig ist, prüfe im Konstruktor new.target und wirf.