В чем разница между абстрактным классомм и интерфейсом?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между Абстрактным Классом и Интерфейсом
Для Frontend Developer с TypeScript это важная концепция для построения архитектуры. Абстрактные классы и интерфейсы служат разным целям, хотя обе используются для определения контрактов.
Интерфейс (Interface)
Interface — это контракт, который определяет структуру объекта (какие методы и свойства он должен иметь):
interface User {
id: string;
name: string;
email: string;
getFullName(): string;
}
// Любой класс может реализовать этот интерфейс
class AdminUser implements User {
id = "1";
name = "Admin";
email = "admin@example.com";
getFullName(): string {
return this.name;
}
}
class GuestUser implements User {
id = "guest";
name = "Guest";
email = "guest@example.com";
getFullName(): string {
return "Guest User";
}
}
Особенности интерфейса:
- Только контракт — не может иметь реализацию методов
- Несколько интерфейсов — класс может implements несколько интерфейсов
- Стирается при компиляции — в JavaScript интерфейс существует только в TypeScript
- Лёгкий — только структура, никакой логики
- Для типизации — основная цель — типобезопасность
Абстрактный класс (Abstract Class)
Abstract class — это класс, который нельзя инстанцировать напрямую, но можно наследоваться:
abstract class Animal {
// Свойства с реализацией
protected name: string;
protected age: number = 0;
// Метод с реализацией
constructor(name: string) {
this.name = name;
}
// Конкретный метод
sleep(): void {
console.log(`${this.name} спит`);
}
// Абстрактный метод (без реализации)
abstract makeSound(): void;
abstract eat(): void;
}
class Dog extends Animal {
makeSound(): void {
console.log("Гав");
}
eat(): void {
console.log("Ест мясо");
}
}
class Cat extends Animal {
makeSound(): void {
console.log("Мяу");
}
eat(): void {
console.log("Ест рыбу");
}
}
// const animal = new Animal("Test"); // Ошибка!
const dog = new Dog("Шарик"); // Ок
dog.sleep();
dog.makeSound();
Особенности абстрактного класса:
- Может иметь реализацию — методы могут быть конкретными
- Может иметь свойства с инициализацией — защищённые данные
- Один родитель — наследоваться можно только от одного класса
- Существует в runtime — можно проверить instanceof
- Для наследования — создание иерархии классов
Сравнение в таблице
| Аспект | Interface | Abstract Class |
|---|---|---|
| Реализация методов | Нет (только объявление) | Может быть |
| Свойства | Только типы | С инициализацией |
| Множественное наследование | Несколько implements | Только один extends |
| Instancирование | Нельзя (не класс) | Нельзя (abstract) |
| Модификаторы доступа | Нет | public, protected, private |
| В runtime | Нет (стирается) | Да |
| instanceof | Нельзя | Можно |
| Конструктор | Нет | Да |
Практические различия
Interface — для контрактов и типов
// Интерфейс определяет, как должен выглядеть объект
interface Logger {
log(message: string): void;
warn(message: string): void;
error(message: string): void;
}
// Разные реализации Logger
class ConsoleLogger implements Logger {
log(message: string) {
console.log(`[INFO] ${message}`);
}
warn(message: string) {
console.warn(`[WARN] ${message}`);
}
error(message: string) {
console.error(`[ERROR] ${message}`);
}
}
class FileLogger implements Logger {
log(message: string) {
fs.writeFile('logs.txt', `[INFO] ${message}`);
}
warn(message: string) {
fs.writeFile('logs.txt', `[WARN] ${message}`);
}
error(message: string) {
fs.writeFile('logs.txt', `[ERROR] ${message}`);
}
}
// Функция работает с любым Logger
function runApp(logger: Logger) {
logger.log("App started");
}
Abstract Class — для иерархии и реиспользования кода
abstract class Repository {
protected db: Database;
constructor(db: Database) {
this.db = db;
}
// Общая логика для всех репозиториев
protected async executeQuery(sql: string) {
console.log(`Executing: ${sql}`);
return this.db.query(sql);
}
// Абстрактные методы, специфичные для каждого репозитория
abstract async findById(id: string): Promise<any>;
abstract async save(entity: any): Promise<void>;
}
class UserRepository extends Repository {
async findById(id: string) {
return this.executeQuery(`SELECT * FROM users WHERE id = '${id}'`);
}
async save(user: any) {
return this.executeQuery(`INSERT INTO users VALUES (${user.id}, '${user.name}')`);
}
}
class ProductRepository extends Repository {
async findById(id: string) {
return this.executeQuery(`SELECT * FROM products WHERE id = '${id}'`);
}
async save(product: any) {
return this.executeQuery(`INSERT INTO products VALUES (${product.id}, '${product.name}')`);
}
}
Когда использовать что
Interface используй когда:
- Нужно определить структуру данных
- Хочешь multiple "наследования" (несколько интерфейсов)
- Фокус на типобезопасности и структуре
- Нет общего кода для переиспользования
interface Comparable {
compareTo(other: Comparable): number;
}
interface Drawable {
draw(): void;
}
class Shape implements Comparable, Drawable {
// ...
}
Abstract Class используй когда:
- Нужна общая реализация для нескольких классов
- Есть иерархия с parent-child отношением
- Хочешь protected свойства и методы
- Нужно управлять состоянием (конструктор)
abstract class Component {
protected state: any;
constructor() {
this.state = {};
}
render(): void {
console.log("Rendering:", this.state);
}
abstract update(): void;
}
Гибридный подход
В TypeScript часто используют оба вместе:
// Интерфейс для контракта
interface PaymentMethod {
process(amount: number): Promise<boolean>;
}
// Абстрактный класс для общей логики
abstract class BasePaymentMethod implements PaymentMethod {
protected logger: Logger;
constructor(logger: Logger) {
this.logger = logger;
}
async process(amount: number): Promise<boolean> {
this.logger.log(`Processing ${amount}`);
const result = await this.executePayment(amount);
return result;
}
protected abstract executePayment(amount: number): Promise<boolean>;
}
class CreditCardPayment extends BasePaymentMethod {
protected async executePayment(amount: number): Promise<boolean> {
// Специфичная логика для карты
return true;
}
}
Вывод
Interface — это контракт ("ЧТО должен реализовать класс"), а Abstract Class — это базовая класс ("ЧТО и КАК должен сделать класс").
Выбирай interface для типобезопасности и контрактов, абстрактные классы для иерархии и переиспользования кода.