Что такое Interface Segregation Principle?
Принцип разделения интерфейса гласит: клиенты не должны зависеть от методов, которые они не используют. Другими словами, лучше иметь несколько узких специализированных интерфейсов, чем один универсальный «толстый» интерфейс, который заставляет классы реализовывать ненужные им методы.
Почему это важно?
Когда класс вынужден реализовывать методы, которые ему не нужны, это приводит к нескольким проблемам:
▪️ Ненужные зависимости — класс зависит от функциональности, которую не использует
▪️ Пустые реализации — приходится писать заглушки или выбрасывать исключения
▪️ Сложность тестирования — нужно мокать методы, которые не используются
▪️ Нарушение SRP — класс получает ответственность, которая ему не нужна
▪️ Хрупкость кода — изменения в неиспользуемых методах могут сломать работу класса
❌Нарушение принципа
Рассмотрим классический пример:
// "Толстый" интерфейс с множеством методов
abstract class Worker {
void work();
void eat();
void sleep();
}
// Человек использует все методы
class Human implements Worker {
@override
void work() {
print('Человек работает');
}
@override
void eat() {
print('Человек ест');
}
@override
void sleep() {
print('Человек спит');
}
}
// Робот вынужден реализовывать ненужные методы
class Robot implements Worker {
@override
void work() {
print('Робот работает');
}
@override
void eat() {
throw Exception('Робот не ест!'); // Бесполезная реализация
}
@override
void sleep() {
throw Exception('Робот не спит!'); // Бесполезная реализация
}
}
Проблема: интерфейс Worker слишком широкий. Робот вынужден реализовывать методы eat() и sleep(), которые ему не нужны, и либо бросает исключения, либо оставляет пустые реализации. Это нарушение ISP.
✅Правильное применение принципа
Разделим «толстый» интерфейс на несколько узких специализированных интерфейсов:
// Узкие интерфейсы с конкретными обязанностями
abstract class Workable {
void work();
}
abstract class Eatable {
void eat();
}
abstract class Sleepable {
void sleep();
}
// Человек реализует все три интерфейса
class Human implements Workable, Eatable, Sleepable {
@override
void work() {
print('Человек работает');
}
@override
void eat() {
print('Человек ест');
}
@override
void sleep() {
print('Человек спит');
}
}
// Робот реализует только то, что ему нужно
class Robot implements Workable {
@override
void work() {
print('Робот работает');
}
}
// Использование
void makeWork(Workable worker) {
worker.work();
}
void feedWorker(Eatable worker) {
worker.eat();
}
void main() {
final human = Human();
final robot = Robot();
makeWork(human); // ✅ Работает
makeWork(robot); // ✅ Работает
feedWorker(human); // ✅ Работает
// feedWorker(robot); // ❌ Ошибка компиляции - правильно!
}
Теперь каждый класс реализует только те интерфейсы, которые ему нужны. Робот не притворяется, что умеет есть и спать. Принцип разделения интерфейса — это про создание чистых и понятных контрактов. Вместо универсальных «комбайнов» создавайте специализированные интерфейсы, каждый из которых отвечает за конкретную роль.
В следующем посте я расскажу о последнем принципе SOLID — принципе инверсии зависимостей (D).