Interface Segregation Principle
Interface Segregation Principle (ISP) — четвёртый принцип SOLID: клиент не должен зависеть от методов, которые он не использует; лучше иметь много узкоспециализированных интерфейсов, чем один «толстый».
Зачем нужно
В JavaScript нет формальных интерфейсов, как в TypeScript или Java, но ISP применим через duck typing и composition. Нарушение ISP приводит к тому, что объекты реализуют методы, которые им не нужны, что усложняет тестирование и создаёт ложные зависимости. Принцип особенно важен при проектировании API библиотек и модулей.
Где используется
- Проектирование ролей и mixin'ов
- Разделение больших классов на специализированные интерфейсы (TypeScript)
- Разработка публичного API модуля
- Применение composition over inheritance
Основной контент
Нарушение ISP
// Плохо: один "жирный" объект-интерфейс
class Animal {
eat {}
sleep {}
fly {} // не все животные летают
swim {} // не все плавают
bark {} // только собаки
}
class Dog extends Animal {
fly { throw new Error('Собаки не летают'); }
// Вынуждены реализовывать ненужный метод
}
Соблюдение ISP через composition
// Хорошо: маленькие focused mixin'ы
const canEat = (Base) => class extends Base {
eat { console.log(`${this.name} ест`); }
};
const canSleep = (Base) => class extends Base {
sleep { console.log(`${this.name} спит`); }
};
const canFly = (Base) => class extends Base {
fly { console.log(`${this.name} летит`); }
};
const canSwim = (Base) => class extends Base {
swim { console.log(`${this.name} плывёт`); }
};
class Animal {
constructor(name) { this.name = name; }
}
// Каждый класс берёт только нужные способности
class Dog extends canEat(canSleep(Animal)) {
bark { console.log('Гав!'); }
}
class Duck extends canEat(canSleep(canFly(canSwim(Animal)))) {}
const dog = new Dog('Рекс');
dog.eat; // 'Рекс ест'
dog.bark; // 'Гав!'
const duck = new Duck('Дак');
duck.fly; // 'Дак летит'
duck.swim; // 'Дак плывёт'
ISP в TypeScript (формальные интерфейсы)
// Нарушение: один большой интерфейс
interface Worker {
work: void;
eat: void;
sleep: void;
}
// Правильно: разделить на роли
interface Workable {
work: void;
}
interface Eatable {
eat: void;
}
interface Sleepable {
sleep: void;
}
class HumanWorker implements Workable, Eatable, Sleepable {
work { console.log('Работает'); }
eat { console.log('Ест'); }
sleep{ console.log('Спит'); }
}
class Robot implements Workable {
work { console.log('Работает без отдыха'); }
// Не обязан реализовывать eat и sleep
}
Частые ошибки
- Создание «god object» — объект с десятками методов, часть которых клиенты никогда не вызывают. Разбивайте на focused модули.
- Принудительная реализация ненужных методов — методы с
throw new Error('Not implemented')— явный признак нарушения ISP. - Смешение ISP с SRP — ISP о клиентах интерфейса, SRP об ответственности класса. Оба важны, но решают разные проблемы.
🎓 Источники
- 🎓 [SOLID ISP — Interface Segregation Principle for JavaScript] · 2024-11-18 · YouTube
- Тезисы: Клиентский код должен зависеть от интерфейса, не от класса — чтобы можно было подменить реализацию. В JS/TS интерфейсы не обязаны наследовать друг друга — структурная типизация. Один инстанс может реализовывать несколько узких интерфейсов одновременно. Классы должны знать только интерфейсы друг друга, не сами классы — тогда модули изолированы.
- «Желательно, чтобы они вообще никак не ссылались друг на друга, они ссылались только на интерфейсы друг друга».