В идеальном мире
В идеально спроектированной системе сущности зависят только от тех интерфейсов, функциональность которых реализуют. Чаще всего это приводит к дроблению интерфейсов на меньшие. Рассмотрим на примерах.
«Пустая» реализация
Допустим, у нас есть класс Programmer
, который описывает программиста из офиса некоторой компании. Сотрудники пишут код и иногда едят пиццу, которую компания заказывает в офис.
interface Programmer {
writeCode(): void
eatPizza(slicesCount: number): void
}
class RegularProgrammer implements Programmer {
constructor() {/*...*/}
writeCode(): void {/*...*/}
eatPizza(slicesCount: number): void {/*...*/}
}
Через какое-то время компания начала нанимать фрилансеров, которые работают удалённо и пиццу не едят. Если мы используем тот же интерфейс, то класс Freelancer
должен будет реализовать метод eatPizza
, хотя он ему и не нужен — это «пустая» реализация интерфейса.
class Freelancer implements Programmer {
constructor() {/*...*/}
writeCode(): void {/*...*/}
eatPizza(slicesCount: number): void {
// здесь будет пусто —
// это сигнал, что интерфейс надо дробить
}
}
Разделение интерфейса
Мы можем избежать проблемы из примера выше, если разделим интерфейс Programmer
. Мы можем поделить его на две роли: CodeProducer
и PizzaConsumer
.
interface CodeProducer {
writeCode(): void
}
interface PizzaConsumer {
eatPizza(slicesCount: number): void
}
Теперь и RegularProgrammer
, и Freelancer
будут реализовывать только те интерфейсы, которые им действительно нужны:
class RegularProgrammer implements CodeProducer, PizzaConsumer {
constructor() {/*...*/}
writeCode(): void {/*...*/}
eatPizza(slicesCount: number): void {/*...*/}
}
class Freelancer implements CodeProducer {
constructor() {/*...*/}
writeCode(): void {/*...*/}
// метод eatPizza уже не нужен
}
Сравнение с SRP, влияние на LSP
ISP можно представлять как принцип единой ответственности (SRP) для интерфейсов. Дробление интерфейсов действительно заставляет делить ответственность между ними.
Если мы применяем ISP, получаем больше интерфейсов с меньшим количеством методов в каждом. Если мы применяем SRP, получаем больше модулей с меньшим количество методов в каждом. Применение обоих принципов разом заставляет делать контракты между модулями проще, что снижает вероятность нарушения принципа подстановки Лисков (LSP).