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 интерфейсы не обязаны наследовать друг друга — структурная типизация. Один инстанс может реализовывать несколько узких интерфейсов одновременно. Классы должны знать только интерфейсы друг друга, не сами классы — тогда модули изолированы.
    • «Желательно, чтобы они вообще никак не ссылались друг на друга, они ссылались только на интерфейсы друг друга».

Связанные темы

Ресурсы