Una classe astratta è una classe base marcata abstract. Non si può creare con new e può dichiarare metodi abstract senza corpo, che ogni sottoclasse deve implementare.
Shape condivide describe() con ogni sottoclasse e obbliga ciascuna a fornire area(). Il tipo Shape[] contiene qualsiasi mix di sottoclassi.
Una classe astratta non si può istanziare
Chiamare new su una classe astratta è un errore di compilazione, perché l'oggetto avrebbe metodi senza implementazione:
index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.
È una regola di compilazione. abstract sparisce dall'output, quindi la Shape emessa è una normale classe JavaScript e del codice JavaScript non tipizzato potrebbe comunque chiamare new Shape(). Quando una libreria ha bisogno di una protezione a runtime, il costruttore può controllare new.target:
Metodi e proprietà astratti
Una sottoclasse deve implementare ogni membro astratto, oppure essere astratta a sua volta. Dimenticarne uno è l'errore TS2515, che suona come Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape', oppure TS2654, che li elenca tutti quando ne mancano diversi.
Campi, getter e metodi possono essere tutti astratti, e possono essere protected:
Un metodo astratto non ha corpo; scriverne uno è l'errore TS1245 (Method 'x' cannot have an implementation because it is marked abstract). Una classe astratta non deve per forza avere membri astratti: abstract da solo impedisce semplicemente l'istanziazione diretta.
Il pattern template method
È il motivo più comune per scrivere una classe astratta: la classe base fissa l'ordine dei passaggi e ogni sottoclasse riempie i passaggi che cambiano.
Le classi astratte possono essere generiche, come qui, così ogni sottoclasse sceglie il tipo di record.
Tipi di costruttore astratti
Per una classe astratta, typeof Shape è un costruttore che non puoi chiamare, quindi un parametro tipizzato typeof Shape non può fare new c(). Quando una funzione deve accettare classi astratte (per esempio per registrarle o controllare instanceof), tipizzala con una firma di costruzione astratta:
abstract new accetta sia classi astratte sia concrete; new da solo accetta solo quelle concrete.
Classe astratta vs interfaccia
| Classe astratta | Interfaccia | |
|---|---|---|
| Contiene codice | sì: metodi, inizializzatori dei campi, costruttore | no, solo tipi |
| Esiste a runtime | sì | no, viene cancellata |
instanceof | funziona | non è possibile |
| Una classe può usarne | una (extends) | molte (implements) |
| Modificatori di accesso | private, protected, public | solo membri pubblici |
| Tipo per oggetti semplici | solo se non ha membri private/protected, e allora instanceof fallisce | sì, qualsiasi valore con quella forma |
La regola pratica: un'interfaccia quando devi solo descrivere una forma (il caso più comune, e funziona anche con gli oggetti letterali), una classe astratta quando le sottoclassi condividono un'implementazione reale e vuoi che il compilatore le obblighi a riempire i vuoti. Le due si combinano bene: un'interfaccia per il contratto pubblico, una classe astratta come base comoda che la implementa. Vedi interfacce vs tipi per l'altro confronto frequente.
Errori comuni
- Usare una classe astratta dove basterebbe un'interfaccia. Se la classe base non contiene codice, è un'interfaccia con peso extra a runtime e il limite dell'ereditarietà singola.
- Provare
static abstract. Non è consentito. I membri statici non possono essere astratti. - Aspettarsi un errore a runtime da
newsu una classe astratta. Non c'è, a meno che tu non scriva il controllo sunew.target. - Chiamare un metodo astratto dal costruttore della classe base. Viene eseguito prima che i campi della sottoclasse siano inizializzati, quindi l'implementazione li vede come
undefined.
Domande frequenti
Che cos'è una classe astratta in TypeScript?
Una classe marcata abstract che non si può creare con new e che può dichiarare membri abstract senza implementazione. Le sottoclassi devono implementare ogni membro astratto. È un modo per condividere codice e imporre una forma allo stesso tempo.
Che differenza c'è tra una classe astratta e un'interfaccia in TypeScript?
Una classe astratta può contenere codice vero (metodi implementati, campi con valori iniziali, un costruttore) ed esiste a runtime, quindi instanceof funziona con lei; una classe può estenderne solo una. Un'interfaccia non contiene codice, viene cancellata in compilazione e una classe può implementarne molte. Usa una classe astratta per condividere comportamento, un'interfaccia per descrivere una forma.
Una classe astratta può avere un costruttore in TypeScript?
Sì. Viene eseguito quando una sottoclasse chiama super(...). Resta comunque impossibile chiamare new sulla classe astratta stessa (TS2511: Cannot create an instance of an abstract class).
I metodi statici possono essere astratti in TypeScript?
No. abstract vale solo per i membri di istanza; static abstract è l'errore TS1243 ('static' modifier cannot be used with 'abstract' modifier). Per imporre una forma statica, tipizza il costruttore con un'interfaccia che includa il membro statico e verifica la classe rispetto a quella.
Una classe astratta viene imposta a runtime?
No. abstract viene cancellato, quindi la classe compilata è JavaScript normale e del semplice codice JavaScript potrebbe comunque chiamare new su di essa. Se è importante, controlla new.target nel costruttore e lancia un errore.