Что означает принцип «зависить от абстракций, а не от конкретных реализаций»? Приведите пример на Java.

«Что означает принцип «зависить от абстракций, а не от конкретных реализаций»? Приведите пример на Java.» — вопрос из категории ООП, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Это принцип инверсии зависимостей (D — Dependency Inversion Principle, часть SOLID). Код верхнего уровня не должен зависеть от деталей нижнего уровня, оба должны зависеть от абстракций (интерфейсов или абстрактных классов). Это повышает гибкость, тестируемость и упрощает замену компонентов.

Пример: Сервис уведомлений.

  1. Определяем абстракцию (интерфейс):
    // Абстракция
    interface MessageSender {
    void send(String recipient, String message);
    }
  2. Создаём конкретные реализации:
    
    // Конкретная реализация 1
    class EmailSender implements MessageSender {
    @Override
    public void send(String recipient, String message) {
        // Логика отправки email
        System.out.println("Email to " + recipient + ": " + message);
    }
    }

// Конкретная реализация 2 class SmsSender implements MessageSender { @Override public void send(String recipient, String message) { // Логика отправки SMS System.out.println("SMS to " + recipient + ": " + message); } }

3.  **Класс высокого уровня зависит от абстракции:**
```java
class NotificationService {
    // Зависимость от интерфейса, а не от EmailSender или SmsSender
    private final MessageSender sender;

    // Внедрение зависимости через конструктор (Dependency Injection)
    public NotificationService(MessageSender sender) {
        this.sender = sender;
    }

    public void notifyUser(String user, String msg) {
        sender.send(user, msg);
    }
}
  1. Использование:
    public class Main {
    public static void main(String[] args) {
        // Легко меняем реализацию, не трогая NotificationService
        MessageSender sender = new EmailSender(); // или new SmsSender()
        NotificationService service = new NotificationService(sender);
        service.notifyUser("user@example.com", "Your order is ready!");
    }
    }

    Ключевые преимущества:

    • Гибкость: NotificationService не знает о деталях отправки.
    • Тестируемость: Можно подменить MessageSender mock-объектом в unit-тестах.
    • Расширяемость: Добавление нового способа отправки (например, PushNotificationSender) не требует изменений в NotificationService.