ISP — что это, принцип разделения интерфейсов в разработке

Автор: IT Sectr Опубликовано: 2026-05-12 Время чтения: 9 мин

ISP (Interface Segregation Principle) — четвёртый принцип SOLID, который утверждает: клиенты не должны зависеть от методов, которые они не используют. Принцип был сформулирован Робертом Мартином в контексте проектирования интерфейсов для объектно-ориентированных систем. Как описано в книге Clean Architecture (2017), принцип разделения интерфейсов требует создавать узкоспециализированные интерфейсы вместо одного универсального, что уменьшает связанность и упрощает внесение изменений.

Главное

  • ISP — принцип разделения интерфейсов, четвёртый в SOLID
  • Клиенты не должны зависеть от методов, которые они не вызывают
  • Толстые интерфейсы (Fat Interfaces) содержат методы, нерелевантные для части клиентов
  • Дробление интерфейсов уменьшает связанность и повышает переиспользование кода
  • ISP тесно связан с SRP и Single Responsibility на уровне интерфейсов

Что такое ISP (Interface Segregation Principle)?

ISP (Interface Segregation Principle) — принцип разделения интерфейсов, который запрещает создавать <<толстые>> интерфейсы с методами, не используемыми всеми клиентами. Вместо одного интерфейса с десятком методов проектируются несколько маленьких интерфейсов, каждый для своей группы клиентов.

Принцип был введён Робертом Мартином как решение проблемы <<загрязнения интерфейсов>>, когда один класс вынужден реализовывать методы, которые ему не нужны, только потому что они объявлены в общем интерфейсе. В статически типизированных языках это выливается в пустые реализации или выброс исключений — прямой признак нарушения ISP.

ISP и SRP дополняют друг друга: SRP про ответственность класса, ISP про контракты интерфейсов. SRP говорит <<один класс — одна причина для изменения>>, ISP — <<один интерфейс — один клиентский сценарий>>. Вместе они формируют модульную архитектуру, где каждый элемент системы имеет чёткие границы.

Толстые интерфейсы и их последствия

Fat Interface — интерфейс, содержащий больше методов, чем нужно конкретному клиенту. Например, интерфейс Worker с методами work, eat, sleep. Робот-рабочий не должен реализовывать eat и sleep, но вынужден. Решение — разделить на Workable, Eatable, Sleepable. Каждый клиент получает ровно то, что ему нужно.

В мобильной разработке толстые интерфейсы встречаются в протоколах делегатов и DataSource. Один протокол может содержать методы для двух разных сценариев (редактирование + отображение), хотя конкретный экран использует только один из них.

Как работает принцип разделения интерфейсов

Реализация ISP начинается с анализа клиентов каждого интерфейса. Если два клиента используют разные наборы методов одного интерфейса — интерфейс следует разделить. Каждый новый интерфейс группирует методы, которые вызываются вместе в рамках одного сценария.

Механизм разделения: исходный интерфейс разбивается на несколько узких, каждый из которых наследует общую часть (если она есть). Клиенты переключаются на зависимость от нужного узкого интерфейса вместо общего. Классы, реализующие исходный интерфейс, теперь реализуют только те узкие интерфейсы, которые им действительно нужны.

Важное уточнение: степень дробления определяется количеством клиентов и их сценариев. ISP не требует максимального дробления (микроинтерфейсы по одному методу). Это привело бы к избыточной сложности. Цель — устранить зависимость клиентов от ненужных методов, а не минимизировать размер каждого интерфейса.

Признаки нарушения ISP

Основные признаки нарушения ISP включают: классы, реализующие интерфейс с пустыми методами (фиктивная реализация), выброс исключения UnsupportedOperationException в реализациях, большое количество параметров или возвращаемых типов, которые не используются частью клиентов, и частые изменения интерфейса, затрагивающие только часть клиентов.

В Android-разработке типовой пример нарушения ISP — интерфейс OnItemClickListener, который включает методы для клика, долгого клика и свайпа. Если конкретный экран использует только клик — остальные методы остаются пустыми. Решение — разделить на OnItemClickListener, OnItemLongClickListener, OnItemSwipeListener.

В iOS-разработке нарушение ISP проявляется в делегатах UIKit: один протокол содержит методы для разных состояний компонента. UITableViewDelegate включает методы для отображения, выделения, редактирования и swipe-action. Часто разработчики реализуют протокол целиком с десятком пустых методов. Разделение на несколько протоколов по группам ответственности решает проблему.

Зависимость от неиспользуемых методов

Проблема не только в эстетике кода. Когда интерфейс меняется (добавляется новый метод), все классы-реализаторы должны быть обновлены — даже те, кому новый метод не нужен. В мобильной разработке с десятками экранов это приводит к каскадным изменениям. ISP изолирует каждый клиент от изменений, которые его не касаются.

Неявное нарушение ISP происходит через параметры конфигурации. Если метод принимает объект с большим количеством полей, а клиент использует только 2-3 из них — это сигнал к дроблению. Альтернатива: несколько специализированных методов с минимальным набором параметров.

В Android-разработке ISP нарушается при использовании одного SharedPreferencesManager для чтения и записи всех настроек приложения. Fragment, которому нужно только прочитать тему, получает зависимость от глобального менеджера с десятком методов для разных типов данных. Разделение на ThemePreferenceProvider, AuthPreferenceProvider, FeatureFlagProvider — применение ISP на уровне сервисов конфигурации. Каждый провайдер содержит ровно те методы, которые нужны его клиентам.

Примеры ISP в мобильных приложениях

Рассмотрим Android-пример с интерфейсом для работы с данными. Нарушение ISP — один интерфейс для всех CRUD операций, хотя не всем клиентам нужны все операции.

kotlin
// Нарушение ISP: толстый интерфейс
interface UserRepository {
    fun getAll(): List<User>
    fun getById(id: Int): User
    fun save(user: User)
    fun delete(id: Int)
}

// После применения ISP: узкие интерфейсы
interface UserReader {
    fun getAll(): List<User>
    fun getById(id: Int): User
}

interface UserWriter {
    fun save(user: User)
    fun delete(id: Int)
}

// ReadOnlyViewModel не зависит от методов записи
class ReadOnlyViewModel(
    private val reader: UserReader
)

iOS-пример с разделением протоколов для работы с медиа:

swift
// Нарушение ISP: один протокол для всей работы с медиа
protocol MediaService {
    func play(url: URL)
    func pause()
    func stop()
    func upload(data: Data) async -> URL
    func download(url: URL) async -> Data
}

// После ISP: разделение на протоколы по ответственности
protocol MediaPlayer {
    func play(url: URL)
    func pause()
    func stop()
}

protocol MediaTransfer {
    func upload(data: Data) async -> URL
    func download(url: URL) async -> Data
}

// PlayerViewModel не зависит от методов загрузки
class PlayerViewModel {
    private let player: MediaPlayer
}

Практический вывод: ISP защищает клиентов от изменений в независящих от них частях интерфейса. Разделение UserRepository на UserReader и UserWriter означает, что изменения в save не затрагивают ReadOnlyViewModel, и наоборот. Каждый клиент изолирован от функциональности, которую не использует, и не требует изменений при доработке других частей системы.

ISP и SRP — естественная пара. SRP определяет, что класс должен иметь одну причину для изменения. ISP применяет ту же логику к интерфейсам: интерфейс должен обслуживать один клиентский сценарий. Класс может реализовывать несколько узких интерфейсов (каждый соответствует одной ответственности), что чище, чем один толстый интерфейс с несколькими ответственностями.

ISP и OCP также связаны: узкие интерфейсы легче расширять. Добавление нового метода в узкий интерфейс затрагивает только его клиентов. Добавление метода в толстый интерфейс затрагивает всех клиентов — потенциально нарушая OCP, если клиенты вынуждены менять свою реализацию.

ISP и DIP работают вместе: DIP требует зависимости от абстракций. ISP делает эти абстракции узкими и сфокусированными. Зависимость от широкого интерфейса — это всё ещё зависимость от абстракции, но <<плохой>> абстракции с точки зрения ISP. Четыре принципа (SRP, OCP, ISP, DIP) формируют <<пирамиду модульности>>: SRP и ISP определяют границы, OCP и DIP — способы расширения и связывания.

Применение ISP в компонентной архитектуре

Компонентная архитектура в мобильных проектах (модули, фичи, слои) выигрывает от ISP на уровне публичных API. Каждый модуль экспортирует узкие интерфейсы для своих потребителей, а не один общий фасад. Это позволяет изменять внутреннюю реализацию модуля без влияния на потребителей, которые используют только часть его функциональности.

В Android-проектах с архитектурой Clean Architecture ISP применяется к UseCase: каждый UseCase — отдельный интерфейс с одним методом invoke или execute. Клиент (ViewModel) зависит только от того UseCase, который ему нужен, а не от целого репозитория. Это делает зависимости прозрачными и тестируемыми.

Часто задаваемые вопросы

Не приводит ли ISP к избыточному количеству интерфейсов?

Да, чрезмерное дробление возможно. ISP не требует одного интерфейса на каждый метод. Критерий: есть ли клиент, которому нужна только часть методов интерфейса. Если все клиенты используют все методы — интерфейс не нужно делить. Оптимальный уровень дробления определяется реальными сценариями использования.

Как ISP применяется к параметрам функций?

ISP на уровне параметров означает: функция не должна принимать объекты с большим количеством полей, если использует только часть из них. Вместо этого стоит передавать только необходимые данные или использовать специализированные интерфейсы (например, интерфейс Renderable вместо полного User).

Чем ISP отличается от LSP?

LSP про корректное наследование и поведенческую совместимость подтипов. ISP про проектирование интерфейсов: клиенты не должны зависеть от методов, которые не используют. LSP отвечает на вопрос «можно ли использовать подкласс вместо базового класса?», ISP — «нужен ли клиенту весь интерфейс целиком?».

Как ISP упрощает тестирование?

Узкие интерфейсы упрощают создание mock-объектов: тест создаёт mock с одним-двумя методами, а не с десятком. Чем меньше методов в интерфейсе, тем проще заглушить его поведение. Это снижает когнитивную нагрузку на разработчика теста и уменьшает вероятность ошибок в mock-логике.

Когда можно отступить от ISP?

Если интерфейс стабилен и все клиенты используют все методы — дробление избыточно. Типичный пример: протоколы UIKit, которые проектировала Apple. Разделять их рискованно, так как UIKit ожидает полной реализации делегата. В таких случаях нарушение ISP оправдано стабильностью API.

Итоги

  • ISP (Interface Segregation Principle) — принцип разделения интерфейсов, четвёртый в SOLID
  • Клиент не должен зависеть от методов, которые он не использует
  • Толстые интерфейсы вынуждают классы реализовывать ненужные методы пустышками или исключениями
  • Разделение интерфейсов уменьшает связанность и изолирует клиентов от изменений
  • ISP + SRP формируют границы модулей: одна ответственность — один узкий контракт
  • Mock-тестирование упрощается: узкий интерфейс требует меньше заглушек
  • Оптимальное дробление определяется реальными клиентскими сценариями, а не максимальным дроблением

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также