Une classe abstraite est une classe de base marquée abstract. Elle ne peut pas être créée avec new, et elle peut déclarer des méthodes abstract sans corps, que chaque sous-classe doit implémenter.
Shape partage describe() avec toutes ses sous-classes et oblige chacune à fournir area(). Le type Shape[] accepte n'importe quel mélange de sous-classes.
Une classe abstraite ne peut pas être instanciée
Appeler new sur une classe abstraite est une erreur de compilation, car l'objet aurait des méthodes sans implémentation :
index.ts(6,11): error TS2511: Cannot create an instance of an abstract class.
C'est une règle de compilation. abstract est effacé de la sortie : le Shape émis est une classe JavaScript ordinaire, et du code JavaScript non typé pourrait toujours appeler new Shape(). Quand une bibliothèque a besoin d'une protection à l'exécution, le constructeur peut vérifier new.target :
Méthodes et propriétés abstraites
Une sous-classe doit implémenter chaque membre abstrait, ou être elle-même abstraite. En oublier un provoque l'erreur TS2515, du genre Non-abstract class 'Square' does not implement inherited abstract member area from class 'Shape', ou TS2654, qui les liste tous quand plusieurs manquent.
Les champs, les getters et les méthodes peuvent tous être abstraits, et ils peuvent être protected :
Une méthode abstraite n'a pas de corps ; en écrire un provoque l'erreur TS1245 (Method 'x' cannot have an implementation because it is marked abstract). Une classe abstraite n'est pas obligée d'avoir des membres abstraits : abstract seul se contente d'empêcher l'instanciation directe.
Le motif template method
La raison la plus courante d'écrire une classe abstraite : la classe de base fixe l'ordre des étapes, et chaque sous-classe remplit les étapes qui varient.
Les classes abstraites peuvent être génériques, comme ici, pour que chaque sous-classe choisisse le type des enregistrements.
Types de constructeurs abstraits
typeof Shape pour une classe abstraite est un constructeur que vous n'avez pas le droit d'appeler : un paramètre typé typeof Shape ne peut donc pas faire new c(). Quand une fonction doit accepter des classes abstraites (par exemple pour les enregistrer ou vérifier instanceof), typez-la avec une signature de construction abstraite :
abstract new accepte les classes abstraites comme concrètes ; un simple new n'accepte que les concrètes.
Classe abstraite ou interface
| Classe abstraite | Interface | |
|---|---|---|
| Contient du code | oui : méthodes, initialiseurs de champs, constructeur | non, seulement des types |
| Existe à l'exécution | oui | non, effacée |
instanceof | fonctionne | impossible |
| Une classe peut en utiliser | une seule (extends) | plusieurs (implements) |
| Modificateurs d'accès | private, protected, public | membres publics uniquement |
| Type pour des objets simples | seulement sans membres private/protected, et instanceof échoue alors | oui, toute valeur ayant la forme |
La règle empirique : une interface quand vous avez seulement besoin de décrire une forme (le cas le plus fréquent, qui fonctionne aussi pour les littéraux objet), une classe abstraite quand les sous-classes partagent une vraie implémentation et que vous voulez que le compilateur les force à combler les manques. Les deux se combinent bien : une interface pour le contrat public, une classe abstraite comme base pratique qui l'implémente. Voir interface ou type pour l'autre comparaison courante.
Erreurs fréquentes
- Utiliser une classe abstraite là où une interface suffirait. Si la classe de base ne contient aucun code, c'est une interface avec un poids supplémentaire à l'exécution et une limite d'héritage simple.
- Essayer
static abstract. Ce n'est pas autorisé. Les membres statiques ne peuvent pas être abstraits. - S'attendre à une erreur à l'exécution en faisant
newsur une classe abstraite. Il n'y en a aucune, sauf si vous écrivez la vérification denew.target. - Appeler une méthode abstraite depuis le constructeur de base. Elle s'exécute avant l'initialisation des champs de la sous-classe, donc l'implémentation les voit à
undefined.
Questions fréquentes
Qu'est-ce qu'une classe abstraite en TypeScript ?
Une classe marquée abstract qui ne peut pas être créée avec new et peut déclarer des membres abstract sans implémentation. Les sous-classes doivent implémenter chaque membre abstrait. C'est un moyen de partager du code et d'imposer une forme en même temps.
Quelle est la différence entre une classe abstraite et une interface en TypeScript ?
Une classe abstraite peut contenir du vrai code (méthodes implémentées, champs avec valeurs initiales, un constructeur) et existe à l'exécution, donc instanceof fonctionne sur elle ; une classe ne peut en étendre qu'une. Une interface ne contient aucun code, est effacée à la compilation, et une classe peut en implémenter plusieurs. Utilisez une classe abstraite pour partager un comportement, une interface pour décrire une forme.
Une classe abstraite peut-elle avoir un constructeur en TypeScript ?
Oui. Il s'exécute quand une sous-classe appelle super(...). Vous ne pouvez toujours pas appeler new sur la classe abstraite elle-même (TS2511: Cannot create an instance of an abstract class).
Les méthodes statiques peuvent-elles être abstraites en TypeScript ?
Non. abstract ne s'applique qu'aux membres d'instance ; static abstract provoque l'erreur TS1243 ('static' modifier cannot be used with 'abstract' modifier). Pour imposer une forme statique, typez le constructeur avec une interface qui inclut le membre statique et vérifiez la classe par rapport à elle.
Une classe abstraite est-elle imposée à l'exécution ?
Non. abstract est effacé : la classe compilée est du JavaScript ordinaire, et du code JavaScript pur pourrait toujours l'instancier avec new. Si cela compte, vérifiez new.target dans le constructeur et levez une exception.