$ sudo teach IT
МОДУЛЬ 8 · УРОК 6

Интерфейсы для классов (implements)

Реализация интерфейсов, множественная реализация и паттерны проектирования

Цели урока

  • Понять, что такое implements и зачем классы реализуют интерфейсы
  • Освоить множественную реализацию интерфейсов
  • Научиться проектировать интерфейсы для классов
  • Разобрать паттерны: Repository, Service через интерфейсы
  • Понять разницу между implements и extends
  • Изучить типичные ошибки и лучшие практики

1. Что такое implements

Ключевое слово implements говорит, что класс обязуется реализовать все методы и свойства, описанные в интерфейсе.

Аналогия:

Интерфейс — это список требований к вакансии. Интерфейс Runnable говорит: «у тебя должен быть метод run(). Класс Runner говорит: «я implements Runnable» — значит, он обещает иметь метод run(). Если забудет — компилятор не пропустит.

// Интерфейс — контракт
interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

// Класс реализует интерфейс
class User implements Serializable {
  constructor(
    public name: string,
    public age: number
  ) {}

  // ✅ Обязательная реализация serialize()
  serialize(): string {
    return JSON.stringify({ name: this.name, age: this.age });
  }

  // ✅ Обязательная реализация deserialize()
  deserialize(data: string): void {
    const parsed = JSON.parse(data);
    this.name = parsed.name;
    this.age = parsed.age;
  }
}

Если класс не реализует все методы интерфейса — ошибка:

// ❌ Ошибка: Class 'User' incorrectly implements interface
class BadUser implements Serializable {
  serialize(): string { return "{}"; }
  // ❌ Забыли deserialize()!
}

2. extends vs implements — в чём разница

extends implements
Используется с Класс → Класс Класс → Интерфейс
Наследование Все методы и свойства Только контракт (сигнатуры)
Количество Только одно (один класс) Несколько интерфейсов
Реализация Наследует готовую Создаёт свою
// extends — наследует реализацию
class Dog extends Animal {
  // Dog получает ВСЕ методы Animal с реализацией
}

// implements — реализует контракт
class Dog2 implements Pet {
  // Dog2 должен САМ реализовать все методы Pet
}

// Можно комбинировать!
class GuideDog extends Dog implements ServiceAnimal {
  // Наследует от Dog, реализует ServiceAnimal
}

3. Множественная реализация интерфейсов

TypeScript позволяет классу реализовывать несколько интерфейсов одновременно — это одна из главных возможностей, которой нет в наследовании:

interface Drawable {
  draw(): void;
}

interface Resizable {
  resize(factor: number): void;
}

interface Clickable {
  onClick(callback: void): void;
}

// Класс реализует ТРИ интерфейса!
class Widget implements Drawable, Resizable, Clickable {
  private size: number = 100;
  private clickHandler?: void;

  draw(): void {
    console.log(`Рисую виджет размером ${this.size}px`);
  }

  resize(factor: number): void {
    this.size *= factor;
    console.log(`Новый размер: ${this.size}px`);
  }

  onClick(callback: void): void {
    this.clickHandler = callback;
  }
}

Преимущество множественной реализации: класс может одновременно «быть» разными «сущностями».

4. Интерфейсы для типизации — полиморфизм через интерфейсы

Интерфейсы позволяют писать полиморфный код: функции принимают интерфейс, а работают с любым классом, который его реализует:

interface Logger {
  log(message: string): void;
}

class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[CONSOLE] ${message}`);
  }
}

class FileLogger implements Logger {
  log(message: string): void {
    // Запись в файл
    console.log(`[FILE] ${message}`);
  }
}

class NullLogger implements Logger {
  log(message: string): void { /* ничего не делает */ }
}

// Функция работает с ЛЮБЫМ Logger
function processData(data: string, logger: Logger): void {
  logger.log(`Обработка: ${data}`);
  // ... обработка данных ...
  logger.log("Готово");
}

// Можно передать любой Logger!
processData("test", new ConsoleLogger());
processData("test", new FileLogger());
processData("test", new NullLogger());

5. Паттерн Repository — работа с данными через интерфейс

// Интерфейс — что должен уметь репозиторий
interface Repository<T> {
  getAll(): T[];
  getById(id: number): T | undefined;
  save(item: T): void;
  delete(id: number): boolean;
}

// Реализация — в памяти (для тестов)
class InMemoryRepository<T extends { id: number }> implements Repository<T> {
  private items: T[] = [];

  getAll(): T[] {
    return [...this.items];
  }

  getById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }

  save(item: T): void {
    this.items.push(item);
  }

  delete(id: number): boolean {
    this.items = this.items.filter(item => item.id !== id);
    return true;
  }
}

// Использование
class User {
  constructor(public id: number, public name: string) {}
}

const userRepo: Repository<User> = new InMemoryRepository<User>();
userRepo.save(new User(1, "Анна"));
userRepo.save(new User(2, "Борис"));
console.log(userRepo.getAll()); // [User{id:1}, User{id:2}]

6. Паттерн Service — бизнес-логика через интерфейс

interface UserService {
  create(name: string, email: string): User;
  findById(id: number): User | undefined;
  findByEmail(email: string): User | undefined;
  delete(id: number): boolean;
}

class UserServiceImpl implements UserService {
  private users: User[] = [];
  private nextId = 1;

  create(name: string, email: string): User {
    // Валидация
    if (this.findByEmail(email)) {
      throw new Error("Email уже существует");
    }
    // Создание
    const user = new User(this.nextId++, name, email);
    this.users.push(user);
    return user;
  }

  findById(id: number): User | undefined {
    return this.users.find(u => u.id === id);
  }

  findByEmail(email: string): User | undefined {
    return this.users.find(u => u.email === email);
  }

  delete(id: number): boolean {
    this.users = this.users.filter(u => u.id !== id);
    return true;
  }
}
Преимущество паттерна Service:

Вы можете заменить UserServiceImpl на мок-реализацию для тестов, не меняя код, который использует UserService. Это принцип «Depend on abstractions, not concretions» (Завись от абстракций, а не от конкретных реализаций).

7. Совместное использование extends и implements

interface Loggable {
  log(): string;
}

interface Comparable<T> {
  compareTo(other: T): number;
}

class Animal {
  constructor(public name: string, public weight: number) {}
}

// Наследуемся от Animal И реализуем Два интерфейса
class Pet extends Animal implements Loggable, Comparable<Pet> {
  constructor(
    name: string,
    weight: number,
    public species: string
  ) {
    super(name, weight);
  }

  log(): string {
    return `${this.species} ${this.name}, ${this.weight} кг`;
  }

  compareTo(other: Pet): number {
    return this.weight - other.weight;
  }
}

8. Частые ошибки

Ошибка 1: Не реализовали все методы интерфейса

// ❌ Class incorrectly implements interface
interface Printable {
  print(): void;
  getPreview(): string;
}

class Document implements Printable {
  print(): void { /* ... */ }
  // ❌ Забыли getPreview()!
}

Ошибка 2: Путаница implements и extends

// ❌ implements с классом — нельзя
// class Dog implements Animal {} // ❌ Animal — это класс, не интерфейс

// ✅ extends для класса
class Dog extends Animal {}

// ✅ implements для интерфейса
class Dog2 implements PetInterface {}

Ошибка 3: Сигнатура метода не совпадает

interface Greeter {
  greet(name: string): string;
}

// ❌ Тип возвращаемого значения не совпадает
class RussianGreeter implements Greeter {
  greet(name: string): void { // ❌ string vs void
    console.log(`Привет, ${name}`);
  }
}

Практическое задание

Создайте систему уведомлений:

  • interface Notifiable — send(title, body), getHistory(): string[]
  • class EmailNotification implements Notifiable — отправляет email
  • class SMSNotification implements Notifiable — отправляет SMS
  • class PushNotification implements Notifiable — push-уведомление
  • Функция sendAlert(notification: Notifiable, title, body) — полиморфная
  • Создайте массив Notifiable[] и отправьте уведомление каждому

Чек-лист урока

  • implements — класс реализует интерфейс, обязуется иметь все методы
  • extends vs implements — extends для классов (наследование), implements для интерфейсов (контракт)
  • Множественная реализация — class X implements A, B, C (несколько интерфейсов)
  • extends + implements — можно комбинировать: class X extends Y implements A, B
  • Полиморфизм через интерфейсы — функции принимают интерфейс, работают с любым классом
  • Паттерн Repository — абстракция доступа к данным через интерфейс
  • Паттерн Service — бизнес-логика за интерфейсом
  • Типизация — переменная типа интерфейса может хранить любой класс, реализующий его
  • Частые ошибки — не реализовали метод, путаница extends/implements, несовпадение сигнатур
  • Принцип — завись от абстракций, а не от конкретных реализаций

Тест по implements и интерфейсам для классов

8 вопросов

Реализация нескольких интерфейсов

Premium

Интерфейс для сериализации

Premium