Абстрактний клас не можна створити напряму (new), лише успадкувати. Він може містити і реалізацію, і абстрактні члени, які зобов'язаний реалізувати нащадок.
abstract class Notifier {
abstract send(to: string, message: string): Promise<void>;
async notifyAll(recipients: string[], message: string) {
for (const to of recipients) {
await this.send(to, message);
}
}
}
class EmailNotifier extends Notifier {
async send(to: string, message: string) {
// реалізація відправки
}
}
new Notifier(); // помилка: абстрактний клас
new EmailNotifier(); // ок
Нащадок, що не реалізував усі абстрактні члени, теж мусить бути abstract.
Чим відрізняється від інтерфейсу:
| Абстрактний клас | Інтерфейс | |
|---|---|---|
| реалізація методів | може мати | ні |
| поля зі значеннями, конструктор | так | ні |
модифікатори protected, private |
так | ні |
| існує під час виконання | так (це клас JavaScript) | ні (зникає при компіляції) |
| кількість на клас | лише один (extends) |
скільки завгодно (implements) |
instanceof |
працює | неможливо |
Коли абстрактний клас:
- є спільна реалізація, а нащадки відрізняються окремими кроками - шаблонний метод: загальний алгоритм у базовому класі, змінні кроки - абстрактні;
- потрібен спільний стан чи конструктор;
- потрібна перевірка
instanceof.
Коли інтерфейс:
- потрібен лише контракт - форма об'єкта чи класу;
- клас має відповідати кільком контрактам;
- об'єкти можуть бути не екземплярами класів (звичайні об'єкти, моки в тестах).
Абстрактні конструктори в типах - щоб прийняти «будь-який підклас», а не сам абстрактний клас:
function register(Ctor: abstract new () => Notifier) {}
Обережно з глибокими ієрархіями: абстрактний клас, від якого успадковують усе, з часом обростає методами «для деяких нащадків». Для повторного використання поведінки часто краща композиція - передати залежність через конструктор.