Питання 7 · Розділ 18

Що таке принцип Interface Segregation?

Принцип розділення інтерфейсів (ISP) гласить: "Клієнти не повинні залежати від методів, які вони не використовують.

Мовні версії: English Russian Ukrainian

Глибоке занурення (Under the Hood)

Принцип розділення інтерфейсів (ISP) гласить: “Клієнти не повинні залежати від методів, які вони не використовують.

Клієнт — клас, який залежить від інтерфейсу (викликає його методи або реалізує його). Якщо інтерфейс змушує клас реалізовувати методи, які йому не потрібні — ISP порушено.”

На рівні байт-коду та JVM великий інтерфейс (Fat Interface) створює зайві залежності в Constant Pool кожного класу-реалізатора. Constant Pool — таблиця посилань у .class файлі. Кожен метод інтерфейсу додає запис. VTable — таблиця віртуальних методів. Більше методів = більша таблиця = трохи більше пам’яті і повільніший виклик. Але головна проблема — архітектурна: “забруднення” коду методами-заглушками.


Senior-інсайт: Role Interfaces vs Header Interfaces

1. Header Interfaces (Антипатерн)

Це коли для кожного класу MyService створюється інтерфейс IMyService, який просто копіює всі його публічні методи.

  • Проблема: Це не абстракція, це просто дублювання. При додаванні методу в сервіс, він додається в інтерфейс, і всі його клієнти змушені перекомпілюватися.

2. Role Interfaces (Senior Approach)

Інтерфейс повинен описувати роль об’єкта в конкретній взаємодії, а не всі його можливості.

  • Один клас може реалізовувати 10 маленьких інтерфейсів-ролей (Serializable, Cloneable, Printable).
  • Клієнт (напр. принтер) вимагає тільки Printable, і йому байдуже, чи вміє об’єкт зберігатися в БД.

Ознаки порушення ISP

  1. UnsupportedOperationException: Найяскравіша ознака. Ви реалізували метод тільки тому, що інтерфейс змусив, але логіки для нього немає.
  2. Порожні методи: Аналогічно, метод просто нічого не робить.
  3. Жирні контракти: Коли для тестування методу вам потрібно мокати 20 функцій в інтерфейсі, хоча метод використовує тільки одну.

Рішення через патерн Adapter

Іноді ви не можете змінити “жирний” інтерфейс сторонньої бібліотеки. В цьому випадку використовуйте Adapter:

// Сторонній жирний інтерфейс
public interface ThirdPartyCloud {
    void upload();
    void delete();
    void list();
    void billing(); // Нам це не потрібно
}

// Наш вузький інтерфейс
public interface SimpleUploader {
    void upload();
}

// Адаптер дотримується ISP для нашого додатку
public class CloudAdapter implements SimpleUploader {
    private final ThirdPartyCloud cloud;
    public void upload() { cloud.upload(); }
}

Продуктивність та Highload

  • Binary Compatibility: ISP підвищує бінарну сумісність. Якщо ви додасте метод у вузький інтерфейс Payment, це зачепить тільки ті сервіси, які реально працюють з платежами, а не весь проект.
  • Cache Locality: Менша кількість методів в інтерфейсі (і, відповідно, у VTable об’єктів) теоретично може трохи краще вкладатися в кеш інструкцій процесора, але на практиці це мікрооптимізація.

Прикордонні випадки (Edge Cases)

  • Default Methods (Java 8+): Дозволяють додавати методи в інтерфейси без поломки реалізацій. Однак це не рятує від порушення ISP. Інтерфейс все одно залишається “жирним”, просто тепер він засмічує простір імен клієнта методами за замовчуванням.
  • Single Method Interfaces: Одно-методні інтерфейси — хороший орієнтир, але не догма. Інтерфейс з 2-3 пов’язаними методами (наприклад, open()/close()/isOpen()) також може бути правильним.

Коли НЕ варто строго слідувати ISP

  1. Інтерфейс з 1-2 методами — немає сенсу дробити далі
  2. Всі реалізації використовують усі методи — розділення не принесе користі
  3. Внутрішній інтерфейс пакету — не видний ззовні, вплив мінімальний

Резюме для Senior

  • Інтерфейси належать клієнтам, а не реалізаціям.
  • Віддавайте перевагу багатьом маленьким інтерфейсам одному великому.
  • Використовуйте Default Methods з обережністю.
  • Пам’ятайте: ISP зменшує зв’язаність (Coupling) і спрощує тестування через зменшення кількості моків.

🎯 Шпаргалка для інтерв’ю

Обов’язково знати:

  • ISP: клієнти не повинні залежати від методів, які вони не використовують
  • Fat Interface змушує класи реалізовувати непотрібні методи → UnsupportedOperationException і заглушки
  • Role Interfaces > Header Interfaces: інтерфейс описує роль, а не всі можливості класу
  • Інтерфейси належать клієнтам, а не реалізаціям
  • Adapter патерн допомагає дотриматися ISP для сторонніх бібліотек
  • Default Methods (Java 8+) не рятують від порушення ISP — просто засмічують простір імен

Часті уточнюючі запитання:

  • Header Interface vs Role Interface? — Header копіює всі методи класу (антипатерн), Role описує конкретну роль (Printable, Serializable)
  • Як ISP спрощує тестування? — Менше моків: тестований клас залежить тільки від потрібних методів
  • Чи потрібно дробити інтерфейс з 1-2 методами? — Ні, подальше дроблення не принесе користі
  • Як ISP пов’язаний з DIP? — Вузькі інтерфейси — стабільніші абстракції, від яких залежать модулі

Червоні прапори (НЕ говорити):

  • “Для кожного класу потрібен свій інтерфейс” (це Header Interface — антипатерн)
  • “Default methods вирішують проблему ISP” (ні, інтерфейс все одно залишається жирним)
  • “Інтерфейс на 20 методів — це нормально” (залежить від того, скільки методів використовує клієнт)

Пов’язані теми:

  • [[8. Що таке принцип Dependency Inversion]]
  • [[3. Що таке принцип Open_Closed]]
  • [[5. Що таке принцип Liskov Substitution]]
  • [[15. Як SOLID допомагає в тестуванні коду]]