Интерфейсы для классов (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 BadUser implements Serializable {
serialize(): string { return "{}"; }
// ❌ Забыли deserialize()!
}
2. extends vs implements — в чём разница
| extends | implements | |
| Используется с | Класс → Класс | Класс → Интерфейс |
| Наследование | Все методы и свойства | Только контракт (сигнатуры) |
| Количество | Только одно (один класс) | Несколько интерфейсов |
| Реализация | Наследует готовую | Создаёт свою |
class Dog extends Animal {
// Dog получает ВСЕ методы Animal с реализацией
}
// implements — реализует контракт
class Dog2 implements Pet {
// Dog2 должен САМ реализовать все методы Pet
}
// Можно комбинировать!
class GuideDog extends Dog implements ServiceAnimal {
// Наследует от Dog, реализует ServiceAnimal
}
3. Множественная реализация интерфейсов
TypeScript позволяет классу реализовывать несколько интерфейсов одновременно — это одна из главных возможностей, которой нет в наследовании:
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. Интерфейсы для типизации — полиморфизм через интерфейсы
Интерфейсы позволяют писать полиморфный код: функции принимают интерфейс, а работают с любым классом, который его реализует:
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 — бизнес-логика через интерфейс
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;
}
}
Вы можете заменить UserServiceImpl на мок-реализацию для тестов, не меняя код, который использует UserService. Это принцип «Depend on abstractions, not concretions» (Завись от абстракций, а не от конкретных реализаций).
7. Совместное использование extends и implements
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: Не реализовали все методы интерфейса
interface Printable {
print(): void;
getPreview(): string;
}
class Document implements Printable {
print(): void { /* ... */ }
// ❌ Забыли getPreview()!
}
Ошибка 2: Путаница implements и extends
// class Dog implements Animal {} // ❌ Animal — это класс, не интерфейс
// ✅ extends для класса
class Dog extends Animal {}
// ✅ implements для интерфейса
class Dog2 implements PetInterface {}
Ошибка 3: Сигнатура метода не совпадает
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— отправляет emailclass SMSNotification implements Notifiable— отправляет SMSclass 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 вопросов