abstract классы
Abstract классы в TypeScript — классы, помеченные ключевым словом
abstract, которые нельзя инстанциировать напрямую; они могут содержать как реализованные методы (шаблон), так и абстрактные члены (abstract), которые обязаны реализовать наследники.
Зачем нужно
Abstract классы задают общий контракт и частичную реализацию для группы классов, когда нужно что-то среднее между интерфейсом (только контракт) и обычным классом (полная реализация). Они идеальны для паттернов Template Method, когда алгоритм общий, а детали — разные.
Где используется
- Паттерн Template Method — общий алгоритм, разные шаги
- Базовые классы для семейства экспортёров, парсеров, обработчиков
- Слой Repository с общей логикой (пагинация, кэш) и абстрактными методами хранилища
- Абстрактные компоненты UI-фреймворков
Основной контент
Базовый синтаксис
abstract class Animal {
// Обычный метод — есть реализация
move: void {
console.log(`${this.name} is moving`);
}
// Абстрактный метод — нет реализации, наследник ОБЯЗАН реализовать
abstract makeSound: string;
// Абстрактное свойство
abstract readonly name: string;
}
// new Animal(); // Error: Cannot create an instance of an abstract class
class Cat extends Animal {
readonly name = "Cat";
makeSound: string {
return "Meow";
}
}
class Dog extends Animal {
readonly name = "Dog";
makeSound: string {
return "Woof";
}
}
const cat = new Cat();
cat.move; // "Cat is moving" — из Animal
cat.makeSound; // "Meow" — реализация Cat
Паттерн Template Method
abstract class DataExporter {
// Шаблонный метод — определяет алгоритм
async export(data: unknown): Promise<void> {
const validated = await this.validate(data);
const transformed = this.transform(validated);
await this.save(transformed);
this.logSuccess(data.length);
}
// Шаги — абстрактные, разные для каждого наследника
protected abstract validate(data: unknown): Promise<unknown>;
protected abstract transform(data: unknown): string;
protected abstract save(content: string): Promise<void>;
// Общий шаг с реализацией
private logSuccess(count: number): void {
console.log(`Exported ${count} items`);
}
}
class CsvExporter extends DataExporter {
protected async validate(data: unknown) { return data; }
protected transform(data: unknown) {
return data.map((row) => JSON.stringify(row)).join("\n");
}
protected async save(content: string) {
await fs.writeFile("output.csv", content);
}
}
class JsonExporter extends DataExporter {
protected async validate(data: unknown) { return data; }
protected transform(data: unknown) { return JSON.stringify(data, null, 2); }
protected async save(content: string) {
await fs.writeFile("output.json()", content);
}
}
Abstract class vs interface
// Interface — только контракт, без реализации
interface Flyable {
fly: void;
}
// Abstract class — контракт + частичная реализация
abstract class Vehicle {
protected speed = 0;
accelerate(delta: number): void {
this.speed += delta; // общая реализация
}
abstract brake: void; // каждый реализует по-своему
}
// Ключевое различие: abstract class может иметь состояние и конструктор
abstract class Base {
constructor(protected readonly id: string) {}
abstract describe: string;
}
abstract конструктор
// Тип «конструктор abstract класса» — для фабрик
type Constructor<T> = new (...args: any) => T;
type AbstractConstructor<T> = abstract new (...args: any) => T;
function createInstance<T>(Cls: Constructor<T>): T {
return new Cls();
}
Частые ошибки
- Инстанциировать abstract класс —
new AbstractClass— ошибка компиляции. - Не реализовать все abstract члены — компилятор потребует реализацию каждого
abstractметода/свойства в конкретном наследнике. - Использовать abstract там где достаточно interface — если нет общей реализации, интерфейс проще и не создаёт рантайм-артефактов.
- Не вызывать
superв наследнике — если abstract класс имеет конструктор, наследник обязан вызватьsuper(...)первым.
Связанные темы
- implements -- реализация интерфейсов
- interface vs type -- когда что
- Parameter Properties
- Расширение интерфейсов -- extends
- _MOC TypeScript