← Назад к вопросам
Приведи пример зависимости от абстракции, а нет от объекта
2.2 Middle🔥 161 комментариев
#SOLID и паттерны проектирования
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Зависимость от абстракции вместо конкретного объекта (DIP)
Проблема: зависимость от конкретной реализации
НЕПРАВИЛЬНО - зависимость от конкретного класса:
// Конкретная реализация
public class PostgresDatabase {
public void saveUser(User user) {
System.out.println("Saving to PostgreSQL: " + user.getName());
}
}
// UserService зависит от PostgresDatabase напрямую
public class UserService {
private PostgresDatabase database; // КОНКРЕТНЫЙ КЛАСС!
public UserService() {
this.database = new PostgresDatabase(); // Жестко привязано
}
public void registerUser(User user) {
database.saveUser(user); // Использую конкретный класс
}
}
Проблемы:
- Если нужно перейти на MySQL, нужно менять UserService
- Невозможно тестировать с подставной реализацией (mock)
- Нарушает DIP: высокоуровневый модуль зависит от низкоуровневого
- Сложно расширять функциональность
Решение: зависимость от абстракции (interface)
ПРАВИЛЬНО - зависимость от interface:
// Абстракция (interface)
public interface Database {
void saveUser(User user);
}
// Конкретная реализация 1: PostgreSQL
public class PostgresDatabase implements Database {
@Override
public void saveUser(User user) {
System.out.println("Saving to PostgreSQL: " + user.getName());
}
}
// Конкретная реализация 2: MySQL
public class MySQLDatabase implements Database {
@Override
public void saveUser(User user) {
System.out.println("Saving to MySQL: " + user.getName());
}
}
// Конкретная реализация 3: MongoDB
public class MongoDatabase implements Database {
@Override
public void saveUser(User user) {
System.out.println("Saving to MongoDB: " + user.getName());
}
}
// UserService зависит от DATABASE INTERFACE, не от конкретного класса!
public class UserService {
private Database database; // INTERFACE!
// Dependency Injection через конструктор
public UserService(Database database) {
this.database = database;
}
public void registerUser(User user) {
database.saveUser(user); // Работает с любой реализацией interface
}
}
Иерархия зависимостей
НЕПРАВИЛЬНО (нарушает DIP):
UserService (высокоуровневый модуль)
↓ зависит от
PostgresDatabase (низкоуровневый модуль)
Высокий уровень зависит от низкого — ПЛОХО!
ПРАВИЛЬНО (соответствует DIP):
UserService (высокоуровневый модуль)
↓ зависит от
Database (абстракция)
↑ реализуется
PostgresDatabase (низкоуровневый модуль)
MySQLDatabase (низкоуровневый модуль)
MongoDatabase (низкоуровневый модуль)
Оба модуля зависят от абстракции — ХОРОШО!
Практический пример: использование
В production коде:
// Используем PostgreSQL
public class ProductionConfig {
public static void main(String[] args) {
Database database = new PostgresDatabase();
UserService userService = new UserService(database);
userService.registerUser(new User("John", "john@example.com"));
// Output: Saving to PostgreSQL: John
}
}
В тестовом коде (подменяю реализацию):
public class UserServiceTest {
@Test
public void testRegisterUserSavesCalled() {
// Mock реализация Database
Database mockDatabase = mock(Database.class);
// Инжектирую mock вместо реальной БД
UserService userService = new UserService(mockDatabase);
User testUser = new User("Alice", "alice@example.com");
userService.registerUser(testUser);
// Проверяю, что saveUser был вызван с правильным аргументом
verify(mockDatabase).saveUser(testUser);
}
}
Переход на другую БД (никаких изменений в UserService!):
// Был PostgreSQL
Database database = new PostgresDatabase();
UserService userService = new UserService(database);
// Меняю на MySQL - UserService не меняется!
Database database = new MySQLDatabase();
UserService userService = new UserService(database);
// Меняю на MongoDB - UserService не меняется!
Database database = new MongoDatabase();
UserService userService = new UserService(database);
Более сложный пример: несколько зависимостей
Неправильно:
public class UserService {
private PostgresDatabase database = new PostgresDatabase(); // КОНКРЕТНЫЙ
private EmailSender emailSender = new GmailSender(); // КОНКРЕТНЫЙ
private Logger logger = new ConsoleLogger(); // КОНКРЕТНЫЙ
public void registerUser(User user) {
logger.log("Registering " + user.getName());
database.saveUser(user);
emailSender.sendWelcomeEmail(user);
}
}
Правильно:
// Абстракции
public interface Database { void saveUser(User user); }
public interface EmailSender { void sendEmail(String to, String subject, String body); }
public interface Logger { void log(String message); }
// Реализации
public class PostgresDatabase implements Database { ... }
public class GmailSender implements EmailSender { ... }
public class ConsoleLogger implements Logger { ... }
// UserService зависит от абстракций
public class UserService {
private Database database;
private EmailSender emailSender;
private Logger logger;
// Все зависимости инжектируются
public UserService(Database database, EmailSender emailSender, Logger logger) {
this.database = database;
this.emailSender = emailSender;
this.logger = logger;
}
public void registerUser(User user) {
logger.log("Registering " + user.getName());
database.saveUser(user);
emailSender.sendEmail(user.getEmail(), "Welcome!", "Welcome aboard!");
}
}
// Использование
Database db = new PostgresDatabase();
EmailSender email = new GmailSender();
Logger log = new ConsoleLogger();
UserService service = new UserService(db, email, log);
service.registerUser(new User("John", "john@example.com"));
С использованием Spring (DI контейнер)
Конфигурация:
@Configuration
public class AppConfig {
@Bean
public Database database() {
return new PostgresDatabase(); // Один класс для всех зависимостей
}
@Bean
public EmailSender emailSender() {
return new GmailSender();
}
@Bean
public Logger logger() {
return new ConsoleLogger();
}
}
// Автоматическая инъекция
@Service
public class UserService {
private Database database;
private EmailSender emailSender;
private Logger logger;
// Spring автоматически подаст зависимости
public UserService(Database database, EmailSender emailSender, Logger logger) {
this.database = database;
this.emailSender = emailSender;
this.logger = logger;
}
public void registerUser(User user) {
logger.log("Registering " + user.getName());
database.saveUser(user);
emailSender.sendEmail(user.getEmail(), "Welcome!", "Welcome aboard!");
}
}
// Использование в другом бине
@Component
public class AdminPanel {
@Autowired
private UserService userService; // Spring инжектирует
public void createUserFromAdmin(User user) {
userService.registerUser(user);
}
}
Тестирование: подмена реализаций
public class UserServiceTest {
private UserService userService;
private Database mockDatabase;
private EmailSender mockEmailSender;
private Logger mockLogger;
@Before
public void setup() {
// Создаю mock реализации
mockDatabase = mock(Database.class);
mockEmailSender = mock(EmailSender.class);
mockLogger = mock(Logger.class);
// Инжектирую mock вместо реальных
userService = new UserService(mockDatabase, mockEmailSender, mockLogger);
}
@Test
public void testUserSavedAndEmailSent() {
User user = new User("John", "john@example.com");
userService.registerUser(user);
// Проверяю, что методы вызваны
verify(mockDatabase).saveUser(user);
verify(mockEmailSender).sendEmail(
eq("john@example.com"),
eq("Welcome!"),
anyString()
);
verify(mockLogger).log(contains("John"));
}
@Test
public void testLoggerCalled() {
User user = new User("Alice", "alice@example.com");
userService.registerUser(user);
// Проверяю параметр логирования
verify(mockLogger).log("Registering Alice");
}
}
Ключевые преимущества
| Аспект | От конкретного | От абстракции |
|---|---|---|
| Изменение реализации | Нужно менять код | Просто подменяю реализацию |
| Тестирование | Сложно (нужны реальные БД) | Легко (использую mocks) |
| Расширяемость | Жестко привязано | Добавляю новую реализацию |
| Переиспользуемость | Привязано к одной БД | Работает с любой БД |
| Изоляция тестов | Тесты влияют друг на друга | Полная изоляция |
| SOLID соответствие | Нарушает DIP | Соответствует DIP |
Итог
Dependency Inversion Principle (DIP) гласит:
- Высокоуровневые модули НЕ должны зависеть от низкоуровневых
- Обе группы должны зависеть от абстракций
- Абстракции НЕ должны зависеть от деталей
- Детали должны зависеть от абстракций
Это достигается через:
- Interfaces/Abstract classes вместо конкретных классов
- Constructor Injection для явного выражения зависимостей
- DI контейнеры (Spring, Guice) для управления жизненным циклом