В идеальном мире

В идеально спроектированной системе сущности зависят только от тех интерфейсов, функциональность которых реализуют. Чаще всего это приводит к дроблению интерфейсов на меньшие. Рассмотрим на примерах.

«Пустая» реализация

Допустим, у нас есть класс 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).

Материалы к разделу

Вопросы