В каких случаях в разработке требуется подмена одного класса другим?

«В каких случаях в разработке требуется подмена одного класса другим?» — вопрос из категории Паттерны, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Подмена реализации (класса) — ключевой принцип гибкого и тестируемого дизайна. Основные случаи:

  1. Тестирование (Изоляция зависимостей): Замена реальных, медленных или недетерминированных компонентов на заглушки (stubs) или моки (mocks).

    // Без подмены: тест зависит от реальной БД
    // С подменой: используем mock-объект
    @Test
    void testService() {
        PaymentGateway mockGateway = mock(PaymentGateway.class);
        when(mockGateway.process(any())).thenReturn(SUCCESS);
        OrderService service = new OrderService(mockGateway); // Внедряем mock
        // Тестируем логику service, не вызывая реальный платёжный шлюз
    }
  2. Смена реализации (Следование интерфейсам): Позволяет менять поведение системы, не изменяя код, который от него зависит (Принцип инверсии зависимостей - DIP).

    interface DataRepository { /* ... */ }
    class SqlRepository implements DataRepository { /* ... */ }
    class InMemoryRepository implements DataRepository { /* ... */ }
    
    // Конфигурация решает, какую реализацию использовать
    DataRepository repo = new InMemoryRepository(); // Для тестов
    DataRepository repo = new SqlRepository();      // Для продакшена
  3. Расширение функциональности (Паттерн Декоратор): Динамическое добавление новой ответственности к объекту.

    interface DataSource { InputStream getData(); }
    class BasicDataSource implements DataSource { /* ... */ }
    // Декоратор добавляет логирование
    class LoggingDataSource implements DataSource {
        private DataSource wrapped;
        public LoggingDataSource(DataSource ds) { this.wrapped = ds; }
        @Override
        public InputStream getData() {
            log("Requesting data..."); // Добавленное поведение
            return wrapped.getData();   // Оригинальное поведение
        }
    }
  4. Адаптация интерфейсов (Паттерн Адаптер): Позволяет объектам с несовместимыми интерфейсами работать вместе.

Ключевой принцип: Программирование на уровне интерфейсов/абстракций, а не конкретных реализаций.