Menu
Coddy logo textTech

Композиция против наследования

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 56 из 91.

В предыдущей главе вы узнали, что трейты обеспечивают возможность «has-a» (имеет), в то время как наследование устанавливает отношение «is-a» (является). Композиция развивает концепцию «has-a» дальше, создавая сложные объекты из более простых и сохраняя их в качестве свойств, а не наследуя от них.

При наследовании Car может расширять Vehicle. При композиции Car содержит объект Engine:

<?php
class Engine {
    public function start(): string {
        return "Engine started";
    }
}

class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine();
    }
    
    public function start(): string {
        return $this->engine->start();
    }
}

$car = new Car();
echo $car->start();

Вывод:

Engine started

Класс Car не наследуется от Engine — он владеет им. Этот подход дает значительные преимущества: вы можете заменить двигатель на другую реализацию, классы остаются слабо связанными, и вы избегаете жестких иерархий, которые создает глубокое наследование.

Общим правилом в ООП является "предпочитайте композицию наследованию." Наследование хорошо работает для подлинных иерархий типов (Dog действительно является Animal), но композиция проявляет себя лучше всего, когда вы собираете функциональность из независимых частей. Вы можете комбинировать несколько объектов, заменять их во время выполнения и тестировать компоненты изолированно.

В следующем уроке рассматривается внедрение зависимостей, которое напрямую основывается на композиции, управляя тем, как объекты получают свои зависимости.

challenge icon

Задание

Легко

Давайте создадим компьютерную систему, используя композицию, где Computer собирается из отдельных объектов-компонентов, а не наследуется от них. Это демонстрирует, как композиция позволяет создавать сложные объекты из более простых, независимых частей.

Вы организуете свой код в трех файлах:

  • Processor.php — Создайте класс Processor, который представляет центральный процессор. Используйте продвижение свойств в конструкторе (constructor promotion) для определения приватного свойства $model (string) и приватного свойства $cores (integer). Добавьте метод getSpecs(), который возвращает "{$this->model} ({$this->cores} cores)".
  • Memory.php — Создайте класс Memory, который представляет оперативную память. Используйте продвижение свойств в конструкторе для определения приватного свойства $size (integer, представляющее ГБ). Добавьте метод getCapacity(), который возвращает "{$this->size}GB RAM".
  • main.php — Подключите оба файла компонентов и создайте класс Computer, использующий композицию. Класс Computer должен:
    • Хранить объект Processor и объект Memory в качестве приватных свойств
    • Принимать модель процессора, количество ядер и объем памяти в своем конструкторе
    • Создавать объекты компонентов внутри себя во время инициализации
    • Иметь метод describe(), который возвращает "Computer with " . $this->processor->getSpecs() . " and " . $this->memory->getCapacity()

    Вы получите три входных значения: модель процессора, количество ядер и объем памяти в ГБ. Создайте объект Computer с этими характеристиками и выведите результат вызова метода describe().

Обратите внимание, что Computer не расширяет Processor или Memory — он содержит их. Эта связь «имеет» (has-a) через композицию сохраняет каждый компонент независимым и пригодным для повторного использования, позволяя Computer делегировать задачи своим частям при необходимости.

Шпаргалка

Композиция строит сложные объекты из более простых, сохраняя их как свойства, а не наследуя от них. Это создает отношение «имеет» (has-a), при котором один класс содержит экземпляры других классов.

Базовый пример композиции:

<?php
class Engine {
    public function start(): string {
        return "Engine started";
    }
}

class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine();
    }
    
    public function start(): string {
        return $this->engine->start();
    }
}

Класс Car не наследуется от Engine — он владеет им. Это обеспечивает слабую связанность классов и позволяет избежать жестких иерархий наследования.

Предпочитайте композицию наследованию: Наследование хорошо работает для подлинных иерархий типов (Dog — это Animal), но композиция проявляет себя лучше всего при сборке функциональности из независимых частей. Вы можете комбинировать несколько объектов, заменять их во время выполнения и тестировать компоненты изолированно.

Попробуйте сами

<?php
require_once 'Processor.php';
require_once 'Memory.php';

class Computer
{
    // TODO: Определите приватные свойства для хранения объектов Processor и Memory

    // TODO: Конструктор должен принимать модель процессора, количество ядер и объем памяти
    // и создавать объекты компонентов внутри
    public function __construct()
    {
        // TODO: Создайте объекты Processor и Memory и сохраните их
    }

    // TODO: Реализуйте метод describe(), который возвращает
    // "Computer with [характеристики процессора] и [объем памяти]"
    public function describe(): string
    {
        // TODO: Делегируйте выполнение объектам компонентов и верните отформатированную строку
    }
}

// Чтение входных данных
$processorModel = trim(fgets(STDIN));
$cores = intval(fgets(STDIN));
$memorySize = intval(fgets(STDIN));

// TODO: Создайте Computer с заданными характеристиками и выведите результат describe()

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование