Принцип разделения интерфейса гласит:
Клиенты не должны зависеть от методов, которые они не используют.
Простыми словами: лучше создать много маленьких, специализированных интерфейсов, чем один огромный и универсальный («толстый»). Когда интерфейс слишком большой, классы, которые его реализуют, вынуждены писать пустые методы-заглушки для функций, которые им на самом деле не нужны.
❌ Нарушение принципа (плохой код)
Представьте, что мы создаем интерфейс для умных устройств SmartDevice. У нас есть навороченный принтер (который умеет всё) и старый черно-белый принтер (который умеет только печатать).
// Огромный "толстый" интерфейс
interface SmartDevice {
print(): void;
scan(): void;
fax(): void;
}
// Навороченный офисный принтер — ему всё подходит
class AllInOnePrinter implements SmartDevice {
public print() { console.log("Печать документа..."); }
public scan() { console.log("Сканирование..."); }
public fax() { console.log("Отправка факса..."); }
}
// Простой принтер на складе — нарушает ISP
class OldPrinter implements SmartDevice {
public print() {
console.log("Печать документа...");
}
// Нарушение ISP: класс вынужден реализовывать методы, которые он не поддерживает!
public scan() {
throw new Error("Я старый принтер, я не умею сканировать!");
}
public fax() {
throw new Error("У меня нет факса!");
}
}Почему это плохо? Класс OldPrinter зависит от функций scan и fax, которых у него физически нет. Если мы случайно вызовем эти методы в коде, программа упадет с ошибкой.
✅ Соблюдение принципа (хороший код)
Мы разделяем один большой интерфейс на три маленьких и независимых. Теперь каждый класс берет (реализует) только то, что ему действительно нужно для работы.
1. Создаем маленькие специализированные интерфейсы
interface Printer {
print(): void;
}
interface Scanner {
scan(): void;
}
interface Fax {
fax(): void;
}2. Реализуем классы без лишнего кода
Благодаря TypeScript, один класс может реализовывать сразу несколько интерфейсов через запятую.
// Старый принтер берет только интерфейс Printer
class OldPrinter implements Printer {
public print() {
console.log("Печать документа на старом принтере...");
}
}
// Современный комбайн реализует ВСЕ интерфейсы разом
class AllInOnePrinter implements Printer, Scanner, Fax {
public print() { console.log("Печать..."); }
public scan() { console.log("Сканирование..."); }
public fax() { console.log("Отправка факса..."); }
}3. Безопасное использование
Теперь функции, работающие с устройствами, точно знают, на что способен объект.
// Функция принимает только то устройство, которое ГАРАНТИРОВАННО умеет сканировать
function processDocument(scanner: Scanner) {
scanner.scan();
}
const superPrinter = new AllInOnePrinter();
const oldPrinter = new OldPrinter();
processDocument(superPrinter); // Всё отлично работает!
// processDocument(oldPrinter); // Ошибка TypeScript! Мы даже не сможем скомпилировать этот код, защитив себя от багов в рантайме.В чем выгода ISP?
- Безопасность типов: Вы никогда не вызовете несуществующий метод у объекта, так как TypeScript подсветит ошибку еще на этапе написания кода.
- Гибкость: Код становится модульным. Классы не перегружены логикой «на всякий случай».
- Чистота: Отсутствуют пустые методы-заглушки со строками
return nullилиthrow new Error().