Какие преимущества и недостатки у композиции перед наследованием в ООП?

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

Ответ

Преимущества композиции ("has-a" отношение):

  • Гибкость и слабая связанность: Позволяет динамически изменять поведение объекта во время выполнения, подменяя компоненты. Классы менее зависимы друг от друга.
  • Инкапсуляция: Внутренняя структура и реализация компонентов полностью скрыты от содержащего их объекта. Доступ происходит через четко определенный интерфейс.
  • Избегание проблем наследования: Решает проблему хрупкого базового класса (fragile base class) и исключает ограничения, связанные с отсутствием множественного наследования в языках вроде Java.
  • Упрощение тестирования: Компоненты можно тестировать изолированно, а в содержащем классе — использовать моки или заглушки.

Недостатки / Сложности композиции:

  • Большее количество классов: Проект может содержать множество мелких классов, что увеличивает сложность навигации.
  • Делегирование вызовов: Требуется явно реализовывать методы, которые просто делегируют вызовы внутренним объектам, что может привести к шаблонному коду.
  • Инициализация и управление жизненным циклом: Создание и настройка составного объекта может быть сложнее, чем создание объекта через наследование.

Пример на Java:

// Компоненты
class Engine {
    public void start() { System.out.println("Двигатель запущен"); }
}

class Transmission {
    public void shiftGear(int gear) { System.out.println("Включена передача: " + gear); }
}

// Класс, использующий композицию
class Car {
    // Композиция: Car HAS-A Engine и HAS-A Transmission
    private final Engine engine;
    private final Transmission transmission;

    public Car(Engine engine, Transmission transmission) {
        this.engine = engine;
        this.transmission = transmission;
    }

    // Делегирование ответственности компонентам
    public void startCar() {
        engine.start();
        transmission.shiftGear(1);
    }

    // Легко заменить компонент, например, на TurboEngine
    public void setEngine(Engine newEngine) {
        this.engine = newEngine;
    }
}