← Назад к вопросам

Приведи пример зависимости от абстракции, а нет от объекта

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) гласит:

  • Высокоуровневые модули НЕ должны зависеть от низкоуровневых
  • Обе группы должны зависеть от абстракций
  • Абстракции НЕ должны зависеть от деталей
  • Детали должны зависеть от абстракций

Это достигается через:

  1. Interfaces/Abstract classes вместо конкретных классов
  2. Constructor Injection для явного выражения зависимостей
  3. DI контейнеры (Spring, Guice) для управления жизненным циклом