← Назад к вопросам

Что такое Interface Segregation в SOLID?

1.8 Middle🔥 221 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Interface Segregation в SOLID

Interface Segregation Principle (ISP) — это четвёртый принцип SOLID. Его суть: клиенты не должны зависеть от интерфейсов, которые они не используют. Лучше иметь много специфичных интерфейсов, чем один универсальный.

Суть принципа

ISP формулируется так:

"Много специфичных для клиента интерфейсов лучше, чем один универсальный интерфейс."

Вместо того, чтобы создать огромный интерфейс со всеми возможными методами, нужно разделить его на меньшие, более специфичные интерфейсы.

Проблема: Нарушение ISP

Пример BAD CODE:

// ❌ Плохо — один большой интерфейс
public interface Worker {
    void work();           // Выполнять работу
    void eat();            // Есть
    void sleep();          // Спать
    void drive();          // Водить машину
    void writeCode();      // Писать код
    void managePeople();   // Управлять людьми
}

// Robot должен реализовать все методы, хотя многие не применимы
public class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Robot работает");
    }
    
    @Override
    public void eat() {
        // Robot не ест! Но нужно реализовать
        throw new UnsupportedOperationException("Robots don't eat");
    }
    
    @Override
    public void sleep() {
        // Robot не спит!
        throw new UnsupportedOperationException("Robots don't sleep");
    }
    
    @Override
    public void drive() {
        // Robot может ездить, но это необязательно
        throw new UnsupportedOperationException("This robot can't drive");
    }
    
    @Override
    public void writeCode() {
        System.out.println("Robot пишет код");
    }
    
    @Override
    public void managePeople() {
        // Robot не может управлять людьми
        throw new UnsupportedOperationException("Robots can't manage people");
    }
}

public class Driver implements Worker {
    @Override
    public void work() {
        System.out.println("Driver работает");
    }
    
    @Override
    public void eat() {
        System.out.println("Driver ест");
    }
    
    @Override
    public void sleep() {
        System.out.println("Driver спит");
    }
    
    @Override
    public void drive() {
        System.out.println("Driver едет");
    }
    
    @Override
    public void writeCode() {
        // Driver не пишет код!
        throw new UnsupportedOperationException("Drivers don't write code");
    }
    
    @Override
    public void managePeople() {
        // Driver может не управлять людьми
        throw new UnsupportedOperationException("This driver doesn't manage");
    }
}

Проблемы этого подхода:

  1. Принуждение реализовывать ненужные методы — Robot вынужден реализовать eat(), sleep()
  2. Ложные исключения — много UnsupportedOperationException
  3. Хрупкий контракт — добавление новых методов ломает все реализации
  4. Плохая читаемость — непонятно, какие методы действительно использует класс
  5. Сложное тестирование — нужно мокировать ненужные методы

Решение: Применение ISP

GOOD CODE — разделяем на специфичные интерфейсы:

// ✅ Специфичный интерфейс для работы
public interface Workable {
    void work();
}

// ✅ Специфичный интерфейс для питания
public interface Eatable {
    void eat();
}

// ✅ Специфичный интерфейс для сна
public interface Sleepable {
    void sleep();
}

// ✅ Специфичный интерфейс для вождения
public interface Driveable {
    void drive();
}

// ✅ Специфичный интерфейс для кодирования
public interface Codeable {
    void writeCode();
}

// ✅ Специфичный интерфейс для управления
public interface Manageable {
    void managePeople();
}

// Robot реализует только необходимые интерфейсы
public class Robot implements Workable, Codeable {
    @Override
    public void work() {
        System.out.println("Robot работает");
    }
    
    @Override
    public void writeCode() {
        System.out.println("Robot пишет код");
    }
}

// Driver реализует только необходимые интерфейсы
public class Driver implements Workable, Eatable, Sleepable, Driveable {
    @Override
    public void work() {
        System.out.println("Driver работает");
    }
    
    @Override
    public void eat() {
        System.out.println("Driver ест");
    }
    
    @Override
    public void sleep() {
        System.out.println("Driver спит");
    }
    
    @Override
    public void drive() {
        System.out.println("Driver едит");
    }
}

// Manager может управлять и работать
public class Manager implements Workable, Manageable, Eatable {
    @Override
    public void work() {
        System.out.println("Manager работает");
    }
    
    @Override
    public void managePeople() {
        System.out.println("Manager управляет людьми");
    }
    
    @Override
    public void eat() {
        System.out.println("Manager ест");
    }
}

Использование:

public class CompanyManagement {
    
    public void makeEveryoneWork(List<Workable> workers) {
        for (Workable worker : workers) {
            worker.work();  // Работают все, не нужны другие методы
        }
    }
    
    public void feedAllEmployees(List<Eatable> eaters) {
        for (Eatable eater : eaters) {
            eater.eat();
        }
    }
    
    public void assignCodingTask(List<Codeable> coders) {
        for (Codeable coder : coders) {
            coder.writeCode();
        }
    }
}

Практический пример: Payment Gateway

BAD CODE — большой интерфейс:

// ❌ Плохо — один огромный интерфейс
public interface PaymentGateway {
    // Основные операции
    boolean charge(double amount);
    boolean refund(double amount);
    
    // Подписки (не все платёжные системы их поддерживают)
    boolean createSubscription(SubscriptionPlan plan);
    boolean cancelSubscription(String subscriptionId);
    
    // Рассрочка (не все поддерживают)
    boolean createInstallmentPlan(double amount, int months);
    
    // Валидация (не все нужна)
    boolean validateCard(String cardNumber);
    
    // Токенизация (не все поддерживают)
    String tokenizeCard(String cardNumber);
}

// Stripe платит очень много, но должен реализовать всё
public class StripePaymentGateway implements PaymentGateway {
    // Stripe поддерживает ВСЁ, но это редко
    @Override
    public boolean charge(double amount) { ... }
    // ... всё остальное
}

// Простая система может не поддерживать подписки
public class SimplePaymentGateway implements PaymentGateway {
    @Override
    public boolean charge(double amount) { ... }
    
    @Override
    public boolean createSubscription(SubscriptionPlan plan) {
        throw new UnsupportedOperationException("Subscriptions not supported");
    }
    // ... много других throw UnsupportedOperationException
}

GOOD CODE — разделённые интерфейсы:

// ✅ Базовый интерфейс для всех
public interface Chargeable {
    boolean charge(double amount);
    boolean refund(double amount);
}

// ✅ Опциональный интерфейс для подписок
public interface SubscriptionProvider {
    boolean createSubscription(SubscriptionPlan plan);
    boolean cancelSubscription(String subscriptionId);
}

// ✅ Опциональный интерфейс для рассрочки
public interface InstallmentProvider {
    boolean createInstallmentPlan(double amount, int months);
}

// ✅ Опциональный интерфейс для валидации
public interface CardValidator {
    boolean validateCard(String cardNumber);
}

// ✅ Опциональный интерфейс для токенизации
public interface CardTokenizer {
    String tokenizeCard(String cardNumber);
}

// Stripe — полнофункциональный провайдер
public class StripePaymentGateway implements 
        Chargeable, 
        SubscriptionProvider, 
        InstallmentProvider, 
        CardValidator, 
        CardTokenizer {
    // Реализует ВСЁ
}

// Простой провайдер — только базовое
public class SimplePaymentGateway implements Chargeable {
    @Override
    public boolean charge(double amount) { ... }
    
    @Override
    public boolean refund(double amount) { ... }
    // Всё! Не нужны другие методы
}

// Провайдер с поддержкой подписок
public class BasicSubscriptionGateway implements Chargeable, SubscriptionProvider {
    // Реализует только две функциональности
}

Использование в бизнес логике:

public class OrderProcessor {
    private Chargeable gateway;  // Требует только Chargeable
    
    public void processPayment(double amount) {
        if (gateway.charge(amount)) {
            System.out.println("Payment successful");
        }
    }
}

public class SubscriptionManager {
    private SubscriptionProvider provider;  // Требует только SubscriptionProvider
    
    public void createUserSubscription(SubscriptionPlan plan) {
        provider.createSubscription(plan);
    }
}

ISP в Spring

// ❌ Плохо — большой сервис
@Service
public class UserService {
    public User getUserById(Long id) { ... }
    public void updateUser(User user) { ... }
    public void deleteUser(Long id) { ... }
    public void sendEmail(String email) { ... }  // Не относится к пользователям!
    public void logActivity(String action) { ... }  // Тоже не относится!
    public void generateReport(LocalDate date) { ... }  // И это!
}

// ✅ Хорошо — разделённые интерфейсы
public interface UserRepository {
    User getUserById(Long id);
    void updateUser(User user);
    void deleteUser(Long id);
}

public interface EmailService {
    void sendEmail(String email);
}

public interface ActivityLogger {
    void logActivity(String action);
}

public interface ReportGenerator {
    void generateReport(LocalDate date);
}

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private EmailService emailService;
    
    @Autowired
    private ActivityLogger logger;
    
    // Каждый компонент отвечает за свою область
}

Преимущества ISP

  1. Меньше зависимостей — класс зависит только от методов, которые он использует
  2. Проще тестировать — можно мокировать только нужные методы
  3. Лучше читаемость — контракт класса ясен и понятен
  4. Гибче расширение — легко добавлять новые интерфейсы
  5. Меньше изменений — добавление нового метода не ломает существующие реализации

Правило большого пальца

Если класс реализует интерфейс и выбрасывает UnsupportedOperationException — это нарушение ISP. Нужно разделить интерфейс на более мелкие.

Вывод

Interface Segregation Principle критичен для создания чистого, масштабируемого кода. Вместо монолитных интерфейсов, которые заставляют классы реализовывать ненужные методы, нужно иметь много специфичных, сфокусированных интерфейсов. Это делает код более гибким, тестируемым и соответствующим принципам SOLID.