← Назад к вопросам
Что такое SOLID dependency injection?
2.0 Middle🔥 211 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
SOLID и Dependency Injection: основы чистого кода
Dependency Injection (DI) и SOLID принципы — это фундаментальные концепции в modern Java разработке. Они работают вместе для создания гибкого, тестируемого и поддерживаемого кода.
Что такое Dependency (зависимость)
// ❌ Плохо: жёсткая зависимость (tight coupling)
public class UserService {
private EmailService emailService = new EmailService(); // создаём сами!
public void registerUser(User user) {
// ... логика регистрации ...
emailService.sendWelcomeEmail(user.getEmail()); // привязаны к EmailService
}
}
// Проблемы:
// - Невозможно тестировать UserService без реального EmailService
// - Если нужен другой EmailService (SMS, Firebase и т.д.), переписываем класс
// - Трудно менять реализацию в runtime
// ✅ Хорошо: Dependency Injection (инъекция зависимостей)
public class UserService {
private NotificationService notificationService; // интерфейс, не реализация!
// Зависимость передаётся снаружи (injection)
public UserService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void registerUser(User user) {
// ... логика регистрации ...
notificationService.sendNotification(user.getEmail()); // используем интерфейс
}
}
// Преимущества:
// - Легко тестировать с mock'ом
// - Легко менять реализацию
// - Гибкая архитектура
Три типа Dependency Injection
1. Constructor Injection (рекомендуется)
public class UserService {
private final EmailService emailService;
private final UserRepository userRepository;
// Зависимости явно указаны в конструкторе
public UserService(EmailService emailService, UserRepository userRepository) {
this.emailService = emailService;
this.userRepository = userRepository;
}
}
// Преимущества:
// + Явная иммутабельность (final)
// + Все зависимости видны сразу
// + Легко тестировать
// + Нет NullPointerException
// + Spring автоматически resolve'ит зависимости
2. Setter Injection
public class UserService {
private EmailService emailService;
// Зависимость устанавливается через setter
public void setEmailService(EmailService emailService) {
this.emailService = emailService;
}
}
// Проблемы:
// - Зависимость необязательна (может быть null)
// - Объект в неполном состоянии после создания
// - Трудно отследить все зависимости
// - Может привести к NullPointerException
// Spring конфигурация:
@Configuration
public class AppConfig {
@Bean
public UserService userService(EmailService emailService) {
UserService service = new UserService();
service.setEmailService(emailService);
return service;
}
}
3. Interface/Field Injection
@Service
public class UserService {
@Autowired // Spring inject'ит зависимость в поле
private EmailService emailService;
}
// Проблемы:
// - Скрытые зависимости (не видны в конструкторе)
// - Невозможно создать объект без Spring
// - Трудно тестировать
// - Нарушает инкапсуляцию
// ❌ Избегай этого подхода
SOLID принципы
SOLID — это аббревиатура пяти принципов для написания хорошего кода.
S — Single Responsibility Principle (SRP)
// ❌ Неправильно: один класс, много ответственности
public class UserService {
// Регистрация пользователя
public void registerUser(User user) { ... }
// Отправка письма
public void sendEmail(String email, String body) { ... }
// Логирование
public void log(String message) { ... }
// Сохранение в БД
public void saveUser(User user) { ... }
}
// ✅ Правильно: разделяем ответственность
public class UserService {
private final UserRepository userRepository;
private final NotificationService notificationService;
private final Logger logger;
public void registerUser(User user) {
userRepository.save(user);
notificationService.sendWelcome(user.getEmail());
logger.info("User registered: " + user.getId());
}
}
public class EmailService implements NotificationService {
public void sendWelcome(String email) { ... }
}
public class UserRepository {
public void save(User user) { ... }
}
O — Open/Closed Principle (OCP)
// ❌ Неправильно: открыт для модификации
public class PaymentProcessor {
public void process(String paymentType, BigDecimal amount) {
if ("CREDIT_CARD".equals(paymentType)) {
// обработка кредитной карты
} else if ("PAYPAL".equals(paymentType)) {
// обработка PayPal
} else if ("BANK_TRANSFER".equals(paymentType)) {
// обработка банковского перевода
}
// Каждый раз добавляем новый else if - ПЛОХО!
}
}
// ✅ Правильно: открыт для расширения, закрыт для модификации
public interface PaymentMethod {
void process(BigDecimal amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void process(BigDecimal amount) {
// обработка кредитной карты
}
}
public class PayPalPayment implements PaymentMethod {
@Override
public void process(BigDecimal amount) {
// обработка PayPal
}
}
public class PaymentProcessor {
private final PaymentMethod paymentMethod;
public PaymentProcessor(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
public void process(BigDecimal amount) {
paymentMethod.process(amount); // один способ для всех
}
}
// Чтобы добавить новый способ оплаты, создаём новый класс
// БЕЗ изменения PaymentProcessor
public class CryptoCurrencyPayment implements PaymentMethod {
@Override
public void process(BigDecimal amount) {
// обработка крипто
}
}
L — Liskov Substitution Principle (LSP)
// ❌ Неправильно: нарушаем контракт родителя
public class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
}
public class Penguin extends Animal {
@Override
public void fly() { // Penguin не может летать!
throw new UnsupportedOperationException("Penguin cannot fly");
}
}
// Проблема: если код ожидает Animal, и получит Penguin,
// всё может сломаться!
// ✅ Правильно: используем правильные абстракции
public interface Animal {
void makeSound();
}
public interface FlyingAnimal extends Animal {
void fly();
}
public class Dog implements Animal {
@Override
public void makeSound() { ... }
}
public class Bird implements FlyingAnimal {
@Override
public void makeSound() { ... }
@Override
public void fly() { ... }
}
public class Penguin implements Animal {
@Override
public void makeSound() { ... }
// Penguin НЕ реализует FlyingAnimal - правильно!
}
I — Interface Segregation Principle (ISP)
// ❌ Неправильно: огромный интерфейс
public interface Worker {
void work();
void eat();
void sleep();
void manage();
void report();
}
public class Robot implements Worker {
@Override
public void work() { ... }
@Override
public void eat() {
throw new UnsupportedOperationException("Robot doesn't eat");
}
@Override
public void sleep() {
throw new UnsupportedOperationException("Robot doesn't sleep");
}
// ... и так далее
}
// ✅ Правильно: разделяем интерфейсы
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
public interface Manageable {
void manage();
}
public class Robot implements Workable {
@Override
public void work() { ... }
}
public class Employee implements Workable, Eatable, Sleepable, Manageable {
@Override
public void work() { ... }
@Override
public void eat() { ... }
// ... и так далее
}
D — Dependency Inversion Principle (DIP)
// ❌ Неправильно: зависит от конкретных классов
public class UserService {
private MySQLDatabase database = new MySQLDatabase(); // высокоуровневый зависит от низкоуровневого!
public User findUser(Long id) {
return database.query("SELECT * FROM users WHERE id = " + id);
}
}
public class MySQLDatabase {
public User query(String sql) { ... }
}
// Проблема: если нужен PostgreSQL, переписываем UserService
// ✅ Правильно: зависит от абстракций
public interface UserRepository { // абстракция
User findById(Long id);
}
public class UserService {
private final UserRepository repository; // зависит от интерфейса
public UserService(UserRepository repository) {
this.repository = repository;
}
public User findUser(Long id) {
return repository.findById(id);
}
}
// Реализации могут быть любыми
public class MySQLUserRepository implements UserRepository {
@Override
public User findById(Long id) { ... }
}
public class PostgresUserRepository implements UserRepository {
@Override
public User findById(Long id) { ... }
}
public class MongoUserRepository implements UserRepository {
@Override
public User findById(Long id) { ... }
}
Spring как DI контейнер
// Spring управляет созданием объектов и их зависимостями
@Repository
public class UserRepository {
public User findById(Long id) { ... }
}
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
User user = userService.findUser(id);
return ResponseEntity.ok(UserMapper.toDTO(user));
}
}
// Spring контейнер (ApplicationContext):
// 1. Создаёт UserRepository
// 2. Создаёт UserService, inject'ит UserRepository
// 3. Создаёт UserController, inject'ит UserService
// 4. Регистрирует Controller как REST endpoint
Тестирование с DI
@Test
void testUserService() {
// Mock зависимость
UserRepository mockRepository = Mockito.mock(UserRepository.class);
User expectedUser = new User(1L, "Alice");
Mockito.when(mockRepository.findById(1L))
.thenReturn(Optional.of(expectedUser));
// Inject'им mock
UserService userService = new UserService(mockRepository);
// Тестируем
User result = userService.findUser(1L);
assertEquals(expectedUser, result);
Mockito.verify(mockRepository).findById(1L);
}
Заключение
SOLID и Dependency Injection вместе создают архитектуру, которая:
- Гибкая — легко менять реализацию
- Тестируемая — легко делать unit тесты
- Поддерживаемая — легко понять и изменить код
- Расширяемая — легко добавлять новые функции
- Масштабируемая — архитектура не разломается при росте
Это стандарт в modern Java разработке, и Spring Boot делает это очень простым и удобным.