Ответ
Принцип «зависимость от абстракций, а не от реализаций» реализуется в 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 даже бровью не поведёт! Он как зависел от абстракции, так и зависит, ему похуй. Главное, чтобы контракт интерфейса соблюдался. Вот это и есть гибкость, ёпта! Не надо переписывать половину проекта из-за одной новой фичи.