Какой механизм Spring Boot реализует принцип «зависимость от абстракций»?

Ответ

Принцип «зависимость от абстракций, а не от реализаций» реализуется в Spring Boot через Dependency Injection (DI), который является частью IoC-контейнера.

Контейнер (ApplicationContext) автоматически связывает конкретные реализации с объявленными зависимостями-интерфейсами, используя аннотации, такие как @Autowired, @Inject или конструктор.

Пример:

// 1. Абстракция (интерфейс)
public interface NotificationService {
    void send(String message);
}

// 2. Конкретная реализация
@Service // Бин регистрируется в контейнере
public class EmailService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

// 3. Клиентский код зависит только от абстракции
@RestController
public class ClientController {
    private final NotificationService service; // Зависимость от интерфейса

    @Autowired // Контейнер внедряет реализацию EmailService
    public ClientController(NotificationService service) {
        this.service = service;
    }

    @GetMapping("/notify")
    public String notifyUser() {
        service.send("Hello!");
        return "Notification sent";
    }
}

Это позволяет легко менять реализацию (например, на SmsService), не изменяя код ClientController.

Ответ 18+ 🔞

Да ты посмотри, какой принцип, блядь, заковыристый! «Зависимость от абстракций, а не от реализаций». Звучит, как какая-то философская хуйня, а на деле — это просто пиздец как удобно, если, конечно, не накосячить.

Вот смотри, в Spring Boot это реализуется через какую-то там Dependency Injection (DI), который является частью этого вашего IoC-контейнера, ёпта.

Этот контейнер, ApplicationContext, он как хитрая жопа — сам всё связывает. Ты ему говоришь: «Слушай, мне нужен NotificationService», а он тебе впендюривает какую-нибудь конкретную реализацию, например, EmailService. И всё это с помощью аннотаций вроде @Autowired. Магия, блядь, да и только!

Пример, чтобы не быть голословным:

// 1. Абстракция (интерфейс)
public interface NotificationService {
    void send(String message);
}

// 2. Конкретная реализация
@Service // Вот тут бин регистрируется в контейнере, запомни это, блядь!
public class EmailService implements NotificationService {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

// 3. Клиентский код, который нихуя не знает о конкретике
@RestController
public class ClientController {
    private final NotificationService service; // Смотри-ка, зависимость от интерфейса!

    @Autowired // А контейнер тут как тут — вмандяривает реализацию EmailService
    public ClientController(NotificationService service) {
        this.service = service;
    }

    @GetMapping("/notify")
    public String notifyUser() {
        service.send("Hello!");
        return "Notification sent";
    }
}

И вся соль, блядь, в чём? А в том, что если завтра тебе понадобится слать не email, а смс, то ты просто создаёшь новый бин SmsService, и клиентский код ClientController даже бровью не поведёт! Он как зависел от абстракции, так и зависит, ему похуй. Главное, чтобы контракт интерфейса соблюдался. Вот это и есть гибкость, ёпта! Не надо переписывать половину проекта из-за одной новой фичи.