Абстрактні класи в TypeScript
Що таке абстрактні класи?
Абстрактний клас — це клас, який не може бути інстанційований безпосередньо — він слугує як базовий клас для інших класів. Він може містити як реалізовані методи, так і абстрактні методи (методи без реалізації, які підкласи повинні реалізувати).
Основний синтаксис
typescript
abstract class Shape {
// Абстрактний метод — без реалізації, повинен бути перевизначений
abstract area(): number;
abstract perimeter(): number;
// Конкретний метод — має реалізацію, успадковується підкласами
describe(): string {
return `Area: ${this.area()}, Perimeter: ${this.perimeter()}`;
}
}
// ❌ Не можна інстанціювати абстрактний клас
const shape = new Shape(); // Помилка!
// ✅ Потрібно розширити та реалізувати абстрактні методи
class Circle extends Shape {
constructor(private radius: number) {
super();
}
area(): number {
return Math.PI * this.radius ** 2;
}
perimeter(): number {
return 2 * Math.PI * this.radius;
}
}
const circle = new Circle(5);
circle.area(); // 78.54
circle.describe(); // "Area: 78.54, Perimeter: 31.42"Абстрактні класи vs Інтерфейси
| Особливість | Абстрактний клас | Інтерфейс |
|---|---|---|
| Реалізація | Може мати реалізовані методи | Без реалізації |
| Конструктор | Так | Ні |
| Модифікатори доступу | Так (public, private, protected) | Ні |
| Властивості з значеннями | Так | Ні |
| Багатократне успадкування | Ні (одне розширення) | Так (багато реалізацій) |
| Існування під час виконання | Так (транспілюється в клас JS) | Ні (знищується під час компіляції) |
typescript
// Інтерфейс — чистий контракт
interface Printable {
print(): void;
}
// Абстрактний клас — контракт + спільна логіка
abstract class BaseRepository<T> {
protected items: T[] = [];
abstract findById(id: string): T | undefined;
getAll(): T[] {
return [...this.items];
}
count(): number {
return this.items.length;
}
}Практичні випадки використання
Шаблон репозиторію
typescript
abstract class BaseRepository<T extends { id: string }> {
protected abstract collection: string;
abstract save(entity: T): Promise<T>;
abstract delete(id: string): Promise<void>;
async findById(id: string): Promise<T | null> {
// Спільна реалізація з використанням this.collection
const response = await fetch(`/api/${this.collection}/${id}`);
return response.json();
}
}
class UserRepository extends BaseRepository<User> {
protected collection = "users";
async save(user: User): Promise<User> {
// Логіка збереження, специфічна для користувача
return user;
}
async delete(id: string): Promise<void> {
// Логіка видалення, специфічна для користувача
}
}Абстрактні властивості
typescript
abstract class Component {
abstract readonly tagName: string;
abstract render(): string;
mount(container: HTMLElement): void {
container.innerHTML = this.render();
}
}
class Button extends Component {
readonly tagName = "button";
render(): string {
return `<${this.tagName}>Click me</${this.tagName}>`;
}
}Коли використовувати абстрактні класи
- Спільна поведінка — загальна логіка, яка потрібна всім підкласам
- Шаблонний метод — визначити структуру алгоритму, дозволити підкласам заповнити кроки
- Стан з поведінкою — коли вам потрібні як властивості, так і методи
- Єдина ієрархія — коли класи природно формують дерево
Коли використовувати інтерфейси замість цього
- Багато контрактів — класу потрібно реалізувати кілька контрактів
- Чиста форма — просто визначення структури даних
- Без спільної логіки — немає спільної реалізації для поділу
- Розв'язування — слабке зв'язування між модулями
Важливо:
Використовуйте абстрактні класи, коли підкласи ділять загальну реалізаційну логіку. Використовуйте інтерфейси, коли вам потрібно лише визначити контракт (форму). На практиці ви часто будете використовувати обидва: абстрактні класи для ієрархій успадкування та інтерфейси для контрактів можливостей.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.